1systemd
2=====
3
4[![Hex.pm](https://img.shields.io/hexpm/v/systemd?style=flat-square)](https://hex.pm/packages/systemd)
5[![HexDocs](https://img.shields.io/badge/HexDocs-docs-blue?style=flat-square)](https://hexdocs.pm/systemd/)
6[![Hex.pm License](https://img.shields.io/hexpm/l/systemd?style=flat-square)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0))
7[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/hauleth/erlang-systemd/Erlang%20CI?style=flat-square)](https://github.com/hauleth/erlang-systemd/actions)
8[![Codecov](https://img.shields.io/codecov/c/gh/hauleth/erlang-systemd?style=flat-square)](https://codecov.io/gh/hauleth/erlang-systemd)
9
10Simple library for notifying systemd about process state.
11
12## Features
13
14- `NOTIFY_SOCKET` communication with supervising process.
15- Watchdog process will be started automatically (if not disabled). It will also
16  handle sending keep-alive messages automatically.
17- Fetching file descritors passed by the supervisor.
18- `journal` logger handler and formatters.
19
20## Installation
21
22Just add this to your `rebar.config`:
23
24```erlang
25{deps, [systemd]}.
26```
27
28Or in case of Mix project, to your `mix.exs`:
29
30```elixir
31defp deps do
32  [
33    {:systemd, "~> 0.6"}
34  ]
35end
36```
37
38Then call `systemd:notify(ready)` when your application is ready to work/accept
39connections or add `systemd:ready()` as a child of your application's main supervisor.
40
41### Non-systemd systems
42
43This application and all functions within are safe to call even in non-systemd
44and non-Linux OSes. In case if there is no systemd configuration options then
45all functions will simply work as (almost) no-ops.
46
47## Usage
48
49Assuming you have `my_app.service` unit like that
50
51```systemd
52[Unit]
53Description=My Awesome App
54
55[Service]
56User=appuser
57Group=appgroup
58# This will allow using `systemd:notify/1` for informing the system supervisor
59# about application status.
60Type=notify
61# Application need to start in foreground instead of forking into background,
62# otherwise it may be not correctly detected and system will try to start it
63# again.
64ExecStart=/path/to/my_app start
65# Enable watchdog process, which will expect messages in given timeframe,
66# otherwise it will restart the process as a defunct. It should be managed
67# automatically by `systemd` application in most cases and will send messages
68# twice as often as requested.
69#
70# You can force failure by using `systemd:watchdog(trigger)` or manually ping
71# systemd watchdog via `systemd:watchdog(ping)`.
72WatchdogSec=10s
73Restart=on-failure
74
75[Install]
76WantedBy=multi-user.target
77```
78
79You can inform systemd about state of your application. To do so just call:
80
81```erlang
82% Erlang
83systemd:notify(ready).
84```
85
86```elixir
87# Elixir
88:systemd.notify(:ready)
89```
90
91This will make `systemctl start my_app.service` to wait until application is up
92and running.
93
94If you want to restart your application you can notify systemd about it with:
95
96```erlang
97% Erlang
98systemd:notify(reloading).
99```
100
101```elixir
102# Elixir
103:systemd.notify(:reloading)
104```
105
106Message about application shutting down will be handled automatically for you.
107
108For simplification of readiness notification there is `systemd:ready()` function
109that returns child specs for temporary process that can be used as a part of
110your supervision tree to mark the point when application is ready, ex.:
111
112```erlang
113% Erlang
114-module(my_app_sup).
115
116-behaviour(supervisor).
117
118-export([start_link/1,
119         init/1]).
120
121start_link(Opts) ->
122    supervisor:start_link({local, ?MODULE}, ?MODULE, Opts).
123
124init(_Opts) ->
125    SupFlags = #{
126      strategy => one_for_one
127    },
128    Children = [
129      my_app_db:child_spec(),
130      my_app_webserver:child_spec(),
131      systemd:ready(),
132      my_app_periodic_job:child_spec()
133    ],
134
135    {ok, {SupFlags, Children}}.
136```
137
138```elixir
139# Elixir
140defmodule MyProject.Application do
141  use Application
142
143  def start(_type, _opts) do
144    children = [
145      MyProject.Repo,
146      MyProjectWeb.Endpoint,
147      :systemd.ready() # <- IMPORTANT - this is a function call (it returns the proper child spec)
148    ]
149
150    Supervisor.start_link(children, strategy: :one_for_one)
151  end
152end
153```
154
155### Logs
156
157To handle logs you have 2 possible options:
158
159- Output data to standard output or error with special prefixes. This approach
160  is much simpler and straightforward, however do not support structured logging
161  and multiline messages.
162- Use datagram socket with special communication protocol. This requires a
163  little bit more effort to set up, but seamlessly supports structured logging
164  and multiline messages.
165
166This library supports both formats, and it is up to You which one (or
167both?) your app will decide to use.
168
169#### Erlang
170
171##### Standard error
172
173There is `systemd_kmsg_formatter` which formats data using `kmsg`-like level
174prefixes can be used with any logger that outputs to standard output or
175standard error if this is attached to the journal. By default `systemd` library
176will update all handlers that use `logger_std_h` with type `standard_io` or
177`standard_error` that are attached to the journal (it is automatically detected
178via `JOURNAL_STREAM` environment variable). You can disable that behaviour by
179setting:
180
181```erlang
182% Erlang
183[
184  {systemd, [{auto_formatter, false}]}
185].
186```
187
188For custom loggers you can use this formatter by adding new option `parent` to
189the formatter options that will be used as "upstream" formatter, ex.:
190
191```erlang
192logger:add_handler(example_handler, logger_disk_log_h, #{
193  formatter => {systemd_kmsg_formatter, #{parent => logger_formatter,
194                                          template => [msg]},
195  config => #{
196    file => "/var/log/my_app.log"
197  }
198}).
199```
200
201##### Datagram socket
202
203This one requires `systemd` application to be started to spawn some processes
204required for handling sockets, so the best way to handle it is to add predefined
205`systemd` handlers after your application starts:
206
207```erlang
208logger:add_handlers(systemd),
209logger:remove_handler(default).
210```
211
212Be aware that this one is **not** guaranteed to work on non-systemd systems, so
213if You aren't sure if that application will be ran on systemd-enabled OS then
214you shouldn't use it as an only logger solution in your application or you can
215end with no logger attached at all.
216
217This handler **should not** be used with `systemd_kmsg_formatter` as this will
218result with pointless `kmsg`-like prefixes in the log messages.
219
220You can also "manually" configgure handler if you want to configure formatter:
221
222```erlang
223logger:add_handler(my_handler, systemd_journal_h, #{
224  formatter => {my_formatter, FormatterOpts}
225}),
226logger:remove_handler(default).
227```
228
229#### Elixir
230
231This assumes Elixir 1.10+, as earlier versions do not use Erlang's `logger`
232module for dispatching logs.
233
234##### Standard error
235
236`systemd` has Erlang's `logger` backend, which mean that you have 2 ways of
237achieving what is needed:
238
2391. Disable Elixir's backends and just rely on default Erlang's handler:
240  ```elixir
241  # config/config.exs
242  config :logger,
243    backends: [],
244    handle_otp_reports: false,
245    handle_sasl_reports: false
246  ```
247  And then allow `systemd` to make its magic that is used in "regular" Erlang
248  code.
249
2502. "Manually" add handler that will use `systemd_kmsg_formatter`:
251  ```elixir
252  # In application start/2 callback
253  :ok = :logger.add_handler(
254    :my_handler,
255    :logger_std_h,
256    %{formatter: {:systemd_kmsg_formatter, %{}}}
257  )
258  Logger.remove_backend(:console)
259  ```
260
261However remember, that currently (as Elixir 1.11) there is no "Elixir formatter"
262for Erlang's `logger` implementation, so you can end with Erlang-style
263formatting of the metadata in the logs.
264
265##### Datagram socket
266
267You can use Erlang-like approach, which is:
268
269```elixir
270# In application start/2 callback
271:logger.add_handlers(:systemd)
272Logger.remove_backend(:console)
273```
274
275Or you can manually configure the handler:
276
277```elixir
278# In application start/2 callback
279:logger.add_handler(
280  :my_handler,
281  :systemd_journal_h,
282  %{formatter: {MyFormatter, formatter_opts}}
283)
284Logger.remove_backend(:console)
285```
286
287Be aware that this one is **not** guaranteed to work on non-systemd systems, so
288if You aren't sure if that application will be ran on systemd-enabled OS then
289you shouldn't use it as an only logger solution in your application or you can
290end with no logger attached at all.
291
292This handler **should not** be used with `:systemd_kmsg_formatter` as this will
293result with pointless `kmsg`-like prefixes in the log messages.
294
295## License
296
297See [LICENSE](LICENSE).
298