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