├── config ├── prod.exs ├── config.exs ├── dev.exs └── test.exs ├── test ├── test_helper.exs └── exsyslog_test.exs ├── examples └── example1 │ ├── test │ ├── test_helper.exs │ └── example1_test.exs │ ├── .gitignore │ ├── README.md │ ├── lib │ └── example1.ex │ ├── mix.exs │ └── config │ └── config.exs ├── .gitignore ├── .travis.yml ├── scripts └── hexpub.sh ├── CHANGELOG.md ├── mix.lock ├── LICENSE ├── mix.exs ├── lib ├── ex_syslogger │ └── json_formatter.ex └── ex_syslogger.ex └── README.md /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /examples/example1/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | /doc 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /examples/example1/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | -------------------------------------------------------------------------------- /examples/example1/README.md: -------------------------------------------------------------------------------- 1 | Example1 2 | ======== 3 | 4 | ** TODO: Add description ** 5 | -------------------------------------------------------------------------------- /examples/example1/lib/example1.ex: -------------------------------------------------------------------------------- 1 | defmodule Example1 do 2 | require Logger 3 | 4 | def run do 5 | Logger.error("Hello ExSyslogger") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /examples/example1/test/example1_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Example1Test do 2 | use ExUnit.Case 3 | 4 | test "the truth" do 5 | assert 1 + 1 == 2 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: elixir 3 | elixir: 4 | - 1.5.0 5 | - 1.4.0 6 | - 1.3.0 7 | 8 | jobs: 9 | include: 10 | - stage: deploy 11 | otp_release: 19.3 12 | script: skip 13 | deploy: 14 | provider: script 15 | script: ./scripts/hexpub.sh 16 | skip_cleanup: true 17 | on: 18 | tags: true 19 | -------------------------------------------------------------------------------- /scripts/hexpub.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p ~/.hex 4 | echo '{username,<<"'${HEX_USERNAME}'">>}.' > ~/.hex/hex.config 5 | echo '{key,<<"'${HEX_KEY}'">>}.' >> ~/.hex/hex.config 6 | 7 | mkdir -p ~/.config/rebar3 8 | echo '{plugins, [rebar3_hex]}.' > ~/.config/rebar3/rebar.config 9 | 10 | mix hex.user passphrase < :crypto.strong_rand_bytes 19 | |> Base.encode16 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.4.0] - 2017-08-02 2 | 3 | ### Add 4 | - Support of Elixir 1.5 5 | - Throw error if Json format is used but poison is not added as dependency 6 | 7 | ### Changed 8 | - Replace GenEvent with `:gen_event` since it's deprecated in Elixir 1.5 9 | - Update `erlang-syslog` to 1.0.5 10 | 11 | ### Removed 12 | - Drop support for Elixir <= 1.2 13 | 14 | ## [1.3.4] - 2017-08-02 15 | ### Add 16 | - Add travis test 17 | 18 | ## Changed 19 | - Fix example project 20 | 21 | ## [1.3.3] - 2017-01-24 22 | ### Changed 23 | - wider the range of poison that works with this repo 24 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], [], "hexpm"}, 2 | "ex_doc": {:hex, :ex_doc, "0.17.1", "39f777415e769992e6732d9589dc5846ea587f01412241f4a774664c746affbb", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, 4 | "syslog": {:hex, :syslog, "1.0.5", "26f26198d16c2f629e0e064b87b69700ef1a7225d5fba9b0993cb9236b91317e", [:rebar3], [], "hexpm"}} 5 | -------------------------------------------------------------------------------- /examples/example1/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Example1.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :example1, 6 | version: "0.0.1", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type `mix help compile.app` for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type `mix help deps` for more examples and options 29 | defp deps do 30 | [ 31 | {:ex_syslogger, path: "../../"}, 32 | {:poison, ">= 1.5.0"} 33 | ] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | 25 | import_config "#{Mix.env}.exs" 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 22cans Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | utc_log: true, 5 | truncate: 8192, 6 | sync_threshold: 40, 7 | discard_threshold_for_error_logger: 500, 8 | compile_time_purge_level: :debug, 9 | backends: [ 10 | {ExSyslogger, :ex_syslogger_error}, 11 | {ExSyslogger, :ex_syslogger_debug}, 12 | {ExSyslogger, :ex_syslogger_json} 13 | ] 14 | 15 | config :logger, :console, 16 | level: :error, 17 | format: "$date $time [$level] $levelpad$node $metadata $message\n", 18 | metadata: [:module, :line, :function] 19 | 20 | config :logger, :ex_syslogger_error, 21 | level: :error, 22 | format: "$date $time [$level] $levelpad$node $metadata $message", 23 | metadata: [:module, :line, :function], 24 | ident: "MyApplication", 25 | facility: :local0, 26 | option: [:pid, :cons] 27 | 28 | config :logger, :ex_syslogger_debug, 29 | level: :debug, 30 | format: "$date $time [$level] $message", 31 | ident: "MyApplication", 32 | facility: :local1, 33 | option: [:pid, :perror] 34 | 35 | config :logger, :ex_syslogger_json, 36 | level: :debug, 37 | format: "$message", 38 | formatter: ExSyslogger.JsonFormatter, 39 | metadata: [:module, :line, :function], 40 | ident: "MyApplication", 41 | facility: :local1, 42 | option: :pid 43 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | utc_log: true, 5 | truncate: 8192, 6 | sync_threshold: 40, 7 | discard_threshold_for_error_logger: 500, 8 | compile_time_purge_level: :debug, 9 | backends: [ 10 | {ExSyslogger, :ex_syslogger_error}, 11 | {ExSyslogger, :ex_syslogger_debug}, 12 | {ExSyslogger, :ex_syslogger_json} 13 | ] 14 | 15 | config :logger, :console, 16 | level: :error, 17 | format: "$date $time [$level] $levelpad$node $metadata $message\n", 18 | metadata: [:module, :line, :function] 19 | 20 | config :logger, :ex_syslogger_error, 21 | level: :error, 22 | format: "$date $time [$level] $levelpad$node $metadata $message", 23 | metadata: [:module, :line, :function], 24 | ident: "MyApplication Error", 25 | facility: :local0, 26 | option: [:pid, :cons] 27 | 28 | config :logger, :ex_syslogger_debug, 29 | level: :debug, 30 | format: "$date $time [$level] $message", 31 | ident: "MyApplication DEBUG", 32 | facility: :local1, 33 | option: [:pid, :perror] 34 | 35 | config :logger, :ex_syslogger_json, 36 | level: :debug, 37 | format: "$message", 38 | formatter: ExSyslogger.JsonFormatter, 39 | metadata: [:module, :line, :function], 40 | ident: "MyApplication JSON", 41 | facility: :local1, 42 | option: :pid 43 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExSyslogger.Mixfile do 2 | use Mix.Project 3 | 4 | @version "1.4.0" 5 | 6 | def project do 7 | [app: :ex_syslogger, 8 | version: @version, 9 | elixir: "~> 1.3", 10 | build_embedded: Mix.env == :prod, 11 | start_permanent: Mix.env == :prod, 12 | description: description(), 13 | package: package(), 14 | deps: deps(), 15 | docs: [source_ref: "#{@version}", main: "ExSyslogger"], 16 | source_url: "https://github.com/slashmili/ex_syslogger"] 17 | end 18 | 19 | # Configuration for the OTP application 20 | # 21 | # Type `mix help compile.app` for more information 22 | def application do 23 | [applications: [:logger, :poison], 24 | included_applications: [:syslog]] 25 | end 26 | 27 | defp description do 28 | """ 29 | ExSyslogger is an Elixir Logger custom backend to syslog. 30 | """ 31 | end 32 | 33 | defp package do 34 | [ files: ["lib", "mix.exs", "README.md", "LICENSE"], 35 | maintainers: ["Milad Rastian"], 36 | licenses: ["MIT"], 37 | links: %{"GitHub": "https://github.com/slashmili/ex_syslogger"} ] 38 | end 39 | 40 | # Dependencies can be Hex packages: 41 | # 42 | # {:mydep, "~> 0.3.0"} 43 | # 44 | # Or git/path repositories: 45 | # 46 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 47 | # 48 | # Type `mix help deps` for more examples and options 49 | defp deps do 50 | [{:syslog, "~> 1.0.5"}, 51 | {:ex_doc, "~> 0.16", only: :dev}, 52 | {:poison, ">= 1.5.0", optional: true} 53 | ] 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /examples/example1/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | 26 | config :logger, 27 | utc_log: true, 28 | truncate: 8192, 29 | sync_threshold: 40, 30 | discard_threshold_for_error_logger: 500, 31 | compile_time_purge_level: :debug, 32 | backends: [ 33 | :console, 34 | {ExSyslogger, :ex_syslogger_error}, 35 | {ExSyslogger, :ex_syslogger_debug}, 36 | {ExSyslogger, :ex_syslogger_json} 37 | ] 38 | 39 | config :logger, :console, 40 | level: :error, 41 | format: "$date $time [$level] $levelpad$node $metadata $message\n", 42 | metadata: [:module, :line, :function] 43 | 44 | config :logger, :ex_syslogger_error, 45 | level: :error, 46 | format: "$date $time [$level] $levelpad$node $metadata $message", 47 | metadata: [:module, :line, :function], 48 | ident: "MyApplication", 49 | facility: :local0, 50 | option: [:pid, :cons] 51 | 52 | config :logger, :ex_syslogger_debug, 53 | level: :debug, 54 | format: "$date $time [$level] $message", 55 | ident: "MyApplication", 56 | facility: :local1, 57 | option: [:pid, :perror] 58 | 59 | config :logger, :ex_syslogger_json, 60 | level: :debug, 61 | format: "$message", 62 | formatter: ExSyslogger.JsonFormatter, 63 | metadata: [:module, :line, :function], 64 | ident: "MyApplication", 65 | facility: :local1, 66 | option: :pid 67 | -------------------------------------------------------------------------------- /lib/ex_syslogger/json_formatter.ex: -------------------------------------------------------------------------------- 1 | defmodule ExSyslogger.JsonFormatter do 2 | @moduledoc """ 3 | JsonFormatter is formatter that produces a properly JSON object string where the level, message, node, and metadata are JSON object root properties. 4 | 5 | JSON object: 6 | ``` 7 | { 8 | "level": "error", 9 | "message": "hello JSON formatter", 10 | "node": "foo@local", 11 | "module": "MyApp.MyModule", 12 | "function": "do_something/2", 13 | "line": 21 14 | } 15 | ``` 16 | 17 | JSON string: 18 | ``` 19 | {\"level\":\"error\",\"message\":\"hello JSON formatter\",\"node\":\"foo@local\",\"module\":\"MyApp.MyModule\",\"function\":\"do_something\/2\",\"line\":21} 20 | 21 | ``` 22 | """ 23 | 24 | @doc """ 25 | Compiles a format string into an array that the `format/6` can handle. 26 | It uses Logger.Formatter. 27 | """ 28 | @spec compile({atom, atom}) :: {atom, atom} 29 | @spec compile(binary | nil) :: [Logger.Formatter.pattern | binary] 30 | 31 | defdelegate compile(str), to: Logger.Formatter 32 | 33 | @doc """ 34 | Takes a compiled format and injects the level, message, node and metadata and returns a properly formatted JSON object where level, message, node and metadata properties are root JSON properties. Message is formated with Logger.Formatter. 35 | 36 | `config_metadata`: is the metadata that is set on the configuration e.g. "metadata: [:module, :line, :function]". 37 | """ 38 | @spec format({atom, atom} | [Logger.Formatter.pattern | binary], 39 | Logger.level, Logger.message, Logger.Formatter.time, 40 | Keyword.t, list(atom)) :: IO.chardata 41 | 42 | def format(format, level, msg, timestamp, metadata, config_metadata) do 43 | case Code.ensure_loaded(Poison) do 44 | {:error, _} -> throw :add_poison_to_your_deps 45 | _ -> nil 46 | end 47 | metadata = metadata |> Keyword.take(config_metadata) 48 | 49 | msg_str = format 50 | |> Logger.Formatter.format(level, msg, timestamp, metadata) 51 | |> to_string() 52 | 53 | log = %{level: level, message: msg_str, node: node()} 54 | 55 | metadata = Enum.reduce(metadata, log, &add_to_log/2) 56 | {:ok, log_json} = apply(Poison, :encode, [metadata]) 57 | 58 | log_json 59 | end 60 | 61 | 62 | ############################################################################## 63 | # 64 | # Internal functions 65 | 66 | defp add_to_log({_, nil}, log), do: log 67 | defp add_to_log({key, value}, log), do: Map.put(log, key, value) 68 | 69 | end 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ExSyslogger 3 | ====== 4 | 5 | [![Build Status](https://travis-ci.org/slashmili/ex_syslogger.svg?branch=master)](https://travis-ci.org/slashmili/ex_syslogger) 6 | [![Hex.pm](https://img.shields.io/hexpm/v/ex_syslogger.svg)](https://hex.pm/packages/ex_syslogger) 7 | [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](https://hexdocs.pm/ex_syslogger/) 8 | [![Hex.pm](https://img.shields.io/hexpm/dt/ex_syslogger.svg)](https://hex.pm/packages/ex_syslogger) 9 | [![Deps Status](https://beta.hexfaktor.org/badge/all/github/slashmili/ex_syslogger.svg)](https://beta.hexfaktor.org/github/slashmili/ex_syslogger) 10 | [![Hex.pm](https://img.shields.io/hexpm/l/ex_syslogger.svg)]() 11 | 12 | ExSyslogger is custom backend for `Elixir Logger` that logs to syslog by wrapping [erlang-syslog](https://github.com/Vagabond/erlang-syslog/). 13 | 14 | This project is a fork of [exsyslog](https://github.com/22cans/exsyslog). 15 | 16 | ## Requirements 17 | * Elixir ~> 1.3 18 | 19 | ## Features 20 | * Logs to syslog 21 | * Allows adding multiple backends with different configurations (e.g. each backend logs to a different facility with different log level) 22 | * Custom log formatter 23 | * Built-in JSON formatter(`poison` dependency is set to optional and you should explicitly add it to your dependency list) 24 | 25 | ## Installation 26 | 27 | Add `:ex_syslogger` as a dependency in your `mix.exs` file 28 | 29 | ### Elixir 1.5 and above 30 | 31 | ```elixir 32 | defp deps do 33 | [ 34 | {:ex_syslogger, "~> 1.4"} 35 | ] 36 | end 37 | ``` 38 | 39 | ### Elixir ~> 1.4 40 | 41 | ```elixir 42 | defp deps do 43 | [ 44 | {:ex_syslogger, "~> 1.3"} 45 | ] 46 | end 47 | ``` 48 | 49 | Add `:ex_syslogger` to your list of `included_applications`: 50 | 51 | ```elixir 52 | def application do 53 | [included_applications: [:ex_syslogger]] 54 | end 55 | ``` 56 | 57 | ## Configuration 58 | 59 | ExSyslogger is a Logger custom backend, as such, it relies on [Logger](http://elixir-lang.org/docs/stable/logger/) application. 60 | 61 | On your `config.exs` file tell `Logger` that it should add `ExSyslogger` backend 62 | ``` 63 | config :logger, 64 | backends: [ 65 | {ExSyslogger, :ex_syslogger_error}, 66 | {ExSyslogger, :ex_syslogger_debug}, 67 | {ExSyslogger, :ex_syslogger_json} 68 | ] 69 | ``` 70 | 71 | With the configuration above, `Logger` application will add three `ExSyslogger` backend with the name `{ExSyslogger, :ex_syslogger_error}`, `{ExSyslogger, :ex_syslogger_debug}` and `{ExSyslogger, :ex_syslogger_json}`. 72 | 73 | You might notice that instead of just passing the Module name, we're passing a tuple with `{Module name, backend configuration name}`. This allow us to have multiple backends with different configuration. Let's configure the backends: 74 | 75 | ``` 76 | config :logger, :ex_syslogger_error, 77 | level: :error, 78 | format: "$date $time [$level] $levelpad$node $metadata $message", 79 | metadata: [:module, :line, :function], 80 | ident: "MyApplication", 81 | facility: :local0, 82 | option: [:pid, :cons] 83 | 84 | config :logger, :ex_syslogger_debug, 85 | level: :debug, 86 | format: "$date $time [$level] $message", 87 | ident: "MyApplication", 88 | facility: :local1, 89 | option: [:pid, :perror] 90 | 91 | config :logger, :ex_syslogger_json, 92 | level: :debug, 93 | format: "$message", 94 | formatter: ExSyslogger.JsonFormatter, 95 | metadata: [:module, :line, :function], 96 | ident: "MyApplication", 97 | facility: :local1, 98 | option: :pid 99 | ``` 100 | 101 | 102 | ### Backend configuration properties 103 | 104 | * __level__ (optional): the logging level. It defaults to `:info` 105 | * __format__ (optional): Same as `:console` backend ([Logger.Formatter](http://elixir-lang.org/docs/stable/logger/)). It defaults to `"\n$date $time [$level] $levelpad$node $metadata $message\n"` 106 | * __formatter__ (optional): Formatter that will be used to format the log. It default to Logger.Formatter 107 | * __metadata__ (optional): Same as `:console` backend [Logger.Formatter](http://elixir-lang.org/docs/stable/logger/). It defaults to `[]` 108 | * __ident__ (optional): A string that's prepended to every message, and is typically set to the app name. It defaults to `"Elixir"` 109 | * __facility__ (optional): syslog facility to be used. It defaults to `:local0`. More documentation on [erlang-syslog](https://github.com/Vagabond/erlang-syslog/#syslogopenident-logopt-facility---ok-port) 110 | * __option__ (optional): syslog option to be used. It defaults to `:ndelay`. More documentation on [erlang-syslog](https://github.com/Vagabond/erlang-syslog/#syslogopenident-logopt-facility---ok-port) 111 | 112 | ## Custom Formatters 113 | ExSyslogger by default uses [Logger.Formatter](http://elixir-lang.org/docs/stable/logger/Logger.Formatter.html). However, it comes with a [JSON formatter](http://hexdocs.pm/exsyslog/1.0.1) that formats a given log entry to a JSON string. __NOTE__: `ExSyslogger.JsonFormatter` can be use as an example if one wants to build his own formatter. 114 | 115 | To build a custom formatter the formatter needs to implement the following functions: 116 | 117 | `compile(str)` 118 | Compiles a format string 119 | ``` 120 | compile(binary | nil) :: [Logger.Formatter.pattern | binary] 121 | compile({atom, atom}) :: {atom, atom} 122 | ``` 123 | 124 | `format(format, level, msg, timestamp, metadata, config_metadata)` 125 | Takes a compiled format and transforms it on a string that will be pass to syslog 126 | ``` 127 | format({atom, atom} | [Logger.Formatter.pattern | binary], Logger.level, Logger.message, Logger.Formatter.time, Keyword.t, [atom]) :: IO.chardata 128 | ``` 129 | 130 | To add the custom formatter you will need to set the `formatter` property on the configuration as exemplified above with `ExSyslogger.JsonFormatter` 131 | 132 | ## Try it 133 | 134 | In another shell: 135 | 136 | ``` 137 | $ tail -f /var/log/syslog 138 | ``` 139 | 140 | (Mac users) 141 | ``` 142 | $ tail -f /var/log/system.log 143 | ``` 144 | __NOTE__ Mac has a *funny* syslog. Your info logs might not show up. You'll need to configure your Mac syslog. 145 | 146 | Clone the project, go to examples/examples1 and run the project (`$ iex -S mix`). 147 | 148 | ``` 149 | Erlang/OTP 18 [erts-7.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 150 | 151 | Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help) 152 | iex(1)> Example1.run 153 | 2015-09-11 15:26:18.850 [error] nonode@nohost module=Elixir.Example1 function=run/0 line=5 Hello ExSyslogger 154 | :ok 155 | ``` 156 | 157 | You should see on the `tail -f` something similar to: 158 | 159 | `exsyslog_error` backend 160 | ``` 161 | Sep 11 16:26:18 bt.local MyApplication[12833]: 2015-09-11 15:26:18.850 [error] nonode@nohost module=Elixir.Example1 function=run/0 line=5 Hello ExSyslogger 162 | ``` 163 | 164 | `exsyslog_debug` backend 165 | ``` 166 | Sep 11 16:26:18 bt.local MyApplication[12833]: 2015-09-11 15:26:18.850 [error] Hello ExSyslogger 167 | ``` 168 | 169 | `exsyslog_json` backend 170 | ``` 171 | Sep 11 16:26:18 bt.local MyApplication[12833]: {"node":"nonode@nohost","module":"Elixir.Example1","message":"Hello ExSyslogger","line":5,"level":"error","function":"run/0"} 172 | ``` 173 | 174 | The source code is released under the MIT License. Check [LICENSE](LICENSE) for more information. 175 | -------------------------------------------------------------------------------- /lib/ex_syslogger.ex: -------------------------------------------------------------------------------- 1 | defmodule ExSyslogger do 2 | @moduledoc """ 3 | ExSyslogger is custom backend for Elixir Logger that logs to syslog by wrapping `erlang-syslog`. 4 | 5 | ## Features 6 | * Logs to syslog 7 | * Allows adding multiple backends with different configurations (e.g. each backend logs to a different facility with different log level) 8 | * Custom log formatter 9 | * Built-in JSON formatter(`poison` dependency is set to optional and you should explicitly add it to your dependency list) 10 | 11 | ## Installation 12 | 13 | Add `:ex_syslogger` as a dependency in your `mix.exs` file 14 | 15 | ### Elixir 1.5 and above 16 | 17 | ```elixir 18 | defp deps do 19 | [ 20 | {:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"} 21 | ] 22 | end 23 | ``` 24 | 25 | ### Elixir ~> 1.4 26 | 27 | ```elixir 28 | defp deps do 29 | [ 30 | {:ex_syslogger, "~> 1.3"} 31 | ] 32 | end 33 | ``` 34 | 35 | Add `:ex_syslogger` to your list of `included_applications`: 36 | 37 | ```elixir 38 | def application do 39 | [included_applications: [:ex_syslogger]] 40 | end 41 | ``` 42 | 43 | ## Configuration 44 | 45 | ExSyslogger is a Logger custom backend, as such, it relies on [Logger](http://elixir-lang.org/docs/stable/logger/) application. 46 | 47 | On your `config.exs` file tell `Logger` that it should add `ExSyslogger` backend 48 | ``` 49 | config :logger, 50 | backends: [ 51 | {ExSyslogger, :ex_syslogger_error}, 52 | {ExSyslogger, :ex_syslogger_debug}, 53 | {ExSyslogger, :ex_syslogger_json} 54 | ] 55 | ``` 56 | 57 | With the configuration above, `Logger` application will add three `ExSyslogger` backend with the name `{ExSyslogger, :ex_syslogger_error}`, `{ExSyslogger, :ex_syslogger_debug}` and `{ExSyslogger, :ex_syslogger_json}`. 58 | 59 | You might notice that instead of just passing the Module name, we're passing a tuple with `{Module name, backend configuration name}`. This allow us to have multiple backends with different configuration. Let's configure the backends: 60 | 61 | ``` 62 | config :logger, :ex_syslogger_error, 63 | level: :error, 64 | format: "$date $time [$level] $levelpad$node $metadata $message", 65 | metadata: [:module, :line, :function], 66 | ident: "MyApplication", 67 | facility: :local0, 68 | option: [:pid, :cons] 69 | 70 | config :logger, :ex_syslogger_debug, 71 | level: :debug, 72 | format: "$date $time [$level] $message", 73 | ident: "MyApplication", 74 | facility: :local1, 75 | option: [:pid, :perror] 76 | 77 | config :logger, :ex_syslogger_json, 78 | level: :debug, 79 | format: "$message", 80 | formatter: ExSyslogger.JsonFormatter, 81 | metadata: [:module, :line, :function], 82 | ident: "MyApplication", 83 | facility: :local1, 84 | option: :pid 85 | ``` 86 | 87 | 88 | ### Backend configuration properties 89 | 90 | * __level__ (optional): the logging level. It defaults to `:info` 91 | * __format__ (optional): Same as `:console` backend ([Logger.Formatter](http://elixir-lang.org/docs/stable/logger/)). It defaults to `"\n$date $time [$level] $levelpad$node $metadata $message\n"` 92 | * __formatter__ (optional): Formatter that will be used to format the log. It default to Logger.Formatter 93 | * __metadata__ (optional): Same as `:console` backend [Logger.Formatter](http://elixir-lang.org/docs/stable/logger/). It defaults to `[]` 94 | * __ident__ (optional): A string that's prepended to every message, and is typically set to the app name. It defaults to `"Elixir"` 95 | * __facility__ (optional): syslog facility to be used. It defaults to `:local0`. More documentation on [erlang-syslog](https://github.com/Vagabond/erlang-syslog/#syslogopenident-logopt-facility---ok-port) 96 | * __option__ (optional): syslog option to be used. It defaults to `:ndelay`. More documentation on [erlang-syslog](https://github.com/Vagabond/erlang-syslog/#syslogopenident-logopt-facility---ok-port) 97 | 98 | ## Custom Formatters 99 | ExSyslogger by default uses [Logger.Formatter](http://elixir-lang.org/docs/stable/logger/Logger.Formatter.html). However, it comes with a [JSON formatter](http://hexdocs.pm/exsyslog/1.0.1) that formats a given log entry to a JSON string. __NOTE__: `ExSyslogger.JsonFormatter` can be use as an example if one wants to build his own formatter. 100 | 101 | To build a custom formatter the formatter needs to implement the following functions: 102 | 103 | `compile(str)` 104 | Compiles a format string 105 | ``` 106 | compile(binary | nil) :: [Logger.Formatter.pattern | binary] 107 | compile({atom, atom}) :: {atom, atom} 108 | ``` 109 | 110 | `format(format, level, msg, timestamp, metadata, config_metadata)` 111 | Takes a compiled format and transforms it on a string that will be pass to syslog 112 | ``` 113 | format({atom, atom} | [Logger.Formatter.pattern | binary], Logger.level, Logger.message, Logger.Formatter.time, Keyword.t, [atom]) :: IO.chardata 114 | ``` 115 | 116 | To add the custom formatter you will need to set the `formatter` property on the configuration as exemplified above with `ExSyslogger.JsonFormatter` 117 | 118 | ## Try it 119 | 120 | In another shell: 121 | 122 | ``` 123 | $ tail -f /var/log/syslog 124 | ``` 125 | 126 | (Mac users) 127 | ``` 128 | $ tail -f /var/log/system.log 129 | ``` 130 | __NOTE__ Mac has a *funny* syslog. Your info logs might not show up. You'll need to configure your Mac syslog. 131 | 132 | Clone the project, go to examples/examples1 and run the project (`$ iex -S mix`). 133 | 134 | ``` 135 | Erlang/OTP 18 [erts-7.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 136 | 137 | Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help) 138 | iex(1)> Example1.run 139 | 2015-09-11 15:26:18.850 [error] nonode@nohost module=Elixir.Example1 function=run/0 line=5 Hello ExSyslogger 140 | :ok 141 | ``` 142 | 143 | You should see on the `tail -f` something similar to: 144 | 145 | `exsyslog_error` backend 146 | ``` 147 | Sep 11 16:26:18 bt.local MyApplication[12833]: 2015-09-11 15:26:18.850 [error] nonode@nohost module=Elixir.Example1 function=run/0 line=5 Hello ExSyslogger 148 | ``` 149 | 150 | `exsyslog_debug` backend 151 | ``` 152 | Sep 11 16:26:18 bt.local MyApplication[12833]: 2015-09-11 15:26:18.850 [error] Hello ExSyslogger 153 | ``` 154 | 155 | `exsyslog_json` backend 156 | ``` 157 | Sep 11 16:26:18 bt.local MyApplication[12833]: {"node":"nonode@nohost","module":"Elixir.Example1","message":"Hello ExSyslogger","line":5,"level":"error","function":"run/0"} 158 | ``` 159 | 160 | """ 161 | 162 | @behaviour :gen_event 163 | 164 | @default_pattern "$date $time [$level] $levelpad$node $metadata $message\n" 165 | 166 | @doc false 167 | def init({__MODULE__, name}) do 168 | config = get_config(name, []) 169 | 170 | :syslog.start() 171 | {:ok, log} = open_log(config) 172 | 173 | {:ok, %{name: name, log: log, config: config}} 174 | end 175 | 176 | @doc """ 177 | Changes backend configuration. 178 | """ 179 | def handle_call({:configure, options}, 180 | %{name: name, log: log, config: config} = state) do 181 | new_config = get_config(name, options) 182 | 183 | {:ok, log} = 184 | if config.facility !== new_config.facility or 185 | config.ident !== new_config.ident or 186 | config.option !== new_config.option or 187 | config.level !== new_config.level do 188 | 189 | close_log(log) 190 | open_log(new_config) 191 | else 192 | {:ok, log} 193 | end 194 | 195 | new_state = %{state | log: log, config: new_config} 196 | {:ok, :ok, new_state} 197 | end 198 | 199 | @doc """ 200 | Ignore messages where the group leader is in a different node. 201 | """ 202 | def handle_event({_level, gl, _event}, config) when node(gl) != node() do 203 | {:ok, config} 204 | end 205 | 206 | @doc """ 207 | Handles an log event. Ignores the log event if the event level is less than the min log level. 208 | """ 209 | def handle_event({level, _gl, {Logger, msg, timestamp, metadata}}, 210 | %{log: log, config: config} = state) do 211 | min_level = config.level 212 | 213 | if is_nil(min_level) or Logger.compare_levels(level, min_level) != :lt do 214 | priority = level_to_priority(level) 215 | event = format_event(level, msg, timestamp, metadata, config) 216 | :syslog.log(log, priority, event) 217 | end 218 | 219 | {:ok, state} 220 | end 221 | 222 | def handle_event(:flush, state), do: {:ok, state} 223 | 224 | ############################################################################## 225 | # 226 | # Internal functions 227 | 228 | defp level_to_priority(:debug), do: :debug 229 | defp level_to_priority(:info), do: :info 230 | defp level_to_priority(:warn), do: :warning 231 | defp level_to_priority(:error), do: :err 232 | 233 | defp get_config(name, options) do 234 | env = Application.get_env(:logger, name, []) 235 | configs = Keyword.merge(env, options) 236 | Application.put_env(:logger, :ex_syslogger, configs) 237 | 238 | level = Keyword.get(configs, :level, :info) 239 | metadata = Keyword.get(configs, :metadata, []) 240 | facility = Keyword.get(configs, :facility, :local0) 241 | option = Keyword.get(configs, :option, :ndelay) 242 | ident = Keyword.get(configs, :ident, "Elixir") |> String.to_charlist 243 | 244 | formatter = Keyword.get(configs, :formatter, Logger.Formatter) 245 | format_str = Keyword.get(configs, :format, @default_pattern) 246 | format = apply(formatter, :compile, [format_str]) 247 | 248 | %{format: format, 249 | formatter: formatter, 250 | level: level, 251 | metadata: metadata, 252 | ident: ident, 253 | facility: facility, 254 | option: option} 255 | end 256 | 257 | defp open_log(%{ident: ident, facility: facility, option: option}) do 258 | :syslog.open(ident, option, facility) 259 | end 260 | 261 | defp close_log(nil), do: :ok 262 | defp close_log(log) when is_port(log), do: :syslog.close(log) 263 | 264 | defp format_event(level, msg, timestamp, metadata, 265 | %{format: format, 266 | formatter: Logger.Formatter, 267 | metadata: config_metadata}) do 268 | metadata = metadata |> Keyword.take(config_metadata) 269 | 270 | format 271 | |> Logger.Formatter.format(level, msg, timestamp, metadata) 272 | |> to_string() 273 | end 274 | 275 | defp format_event(level, msg, timestamp, metadata, 276 | %{format: format, 277 | formatter: formatter, 278 | metadata: config_metadata}) do 279 | apply(formatter, :format, 280 | [format, level, msg, timestamp, metadata, config_metadata]) 281 | end 282 | 283 | @doc false 284 | def handle_info(_msg, state) do 285 | {:ok, state} 286 | end 287 | 288 | @doc false 289 | def terminate(_reason, _state) do 290 | :ok 291 | end 292 | 293 | @doc false 294 | def code_change(_old, state, _extra) do 295 | {:ok, state} 296 | end 297 | 298 | end 299 | --------------------------------------------------------------------------------