├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs └── test.exs ├── lib └── flex_logger.ex ├── mix.exs ├── mix.lock └── test ├── flex_logger_test.exs ├── support ├── a.ex ├── foo │ └── bar.ex ├── logger_mock_with_name.ex └── logger_mock_without_name.ex └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | *.iml 23 | /.idea/ 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.2.1 (2017-11-03) 4 | 5 | ### Fixes 6 | 7 | * Make `:message` key work with character lists 8 | 9 | ## v0.2.0 (2017-11-01) 10 | 11 | ### Enhancements 12 | 13 | * Added `:message` key to `:level_config` 14 | 15 | ## v0.1.5 (2017-10-30) 16 | 17 | First published version 18 | 19 | ### Enhancements 20 | 21 | * Added changelog -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Arno Mittelbach 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be 11 | included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Hex.pm](https://img.shields.io/hexpm/v/flex_logger.svg?style=flat)](https://hex.pm/packages/flex_logger) 2 | 3 | # FlexLogger 4 | 5 | A flexible logger (backend) that adds module/application specific log levels to Elixir's `Logger`. 6 | 7 | ## Installation 8 | 9 | The package can be installed via hex [https://hex.pm/packages/flex_logger](https://hex.pm/packages/flex_logger) by adding `flex_logger` to your list of dependencies in `mix.exs`: 10 | 11 | ```elixir 12 | def deps do 13 | [ 14 | {:flex_logger, "~> 0.2.1"} 15 | ] 16 | end 17 | ``` 18 | 19 | ## Usage 20 | 21 | Full documentation can be found at [https://hexdocs.pm/flex_logger](https://hexdocs.pm/flex_logger). 22 | 23 | Following is a quick example of how to add FlexLogger to your logging configuration 24 | 25 | ```elixir 26 | config :logger, 27 | backends: [{FlexLogger, :foo_file_logger}, 28 | {FlexLogger, :bar_console_logger}, 29 | {FlexLogger, :default_logger}] 30 | 31 | config :logger, :foo_file_logger, 32 | logger: LoggerFileBackend, # The actual backend to use (for example :console or LoggerFileBackend) 33 | default_level: :off, # this is the loggers default level 34 | level_config: [ # override default levels 35 | [application: :my_app, module: Foo, level: :info] # available keys are :application, :module, :function 36 | ], 37 | path: "/tmp/foo.log", # logger specific configuration 38 | format: "FOO $message" # logger specific configuration 39 | 40 | 41 | config :logger, :bar_console_logger, 42 | logger: :console, 43 | default_level: :off, # this is the loggers default level 44 | level_config: [ # override default levels 45 | [application: :some_app, module: Bar, level: :info], 46 | ], 47 | format: "BAR $message" # logger specific 48 | 49 | config :logger, :default_logger, 50 | logger: :console, 51 | default_level: :debug, # this is the loggers default level 52 | level_config: [ # override default levels 53 | [application: :some_app, module: Bar, level: :off], # not Bar and 54 | [application: :my_app, module: Foo, level: :off], # not Foo 55 | ], 56 | format: "DEFAULT $message" # logger specific 57 | ``` 58 | 59 | ## License 60 | 61 | FlexLogger source code is released under MIT License. 62 | 63 | Check LICENSE file for more information. 64 | 65 | -------------------------------------------------------------------------------- /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 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :flex_logger_backend, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:flex_logger_backend, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | backends: [{FlexLogger, :foo_file_logger}, 5 | {FlexLogger, :bar_console_logger}, 6 | {FlexLogger, :default_logger}] 7 | 8 | config :logger, :foo_file_logger, 9 | logger: LoggerFileBackend, # The actual backend to use (for example :console or LoggerFileBackend) 10 | default_level: :off, # this is the loggers default level 11 | level_config: [ # override default levels 12 | [module: Foo, level: :info] # available keys are :application, :module, :function 13 | ], 14 | path: "/tmp/foo.log", # backend specific configuration 15 | format: "FOO $message" # backend specific configuration 16 | 17 | 18 | config :logger, :bar_console_logger, 19 | logger: :console, 20 | default_level: :off, # this is the loggers default level 21 | level_config: [ # override default levels 22 | [module: Bar, level: :info], 23 | ], 24 | format: "BAR $message" # backend specific 25 | 26 | config :logger, :default_logger, 27 | logger: :console, 28 | default_level: :debug, # this is the loggers default level 29 | level_config: [ # override default levels 30 | [module: Bar, level: :off], # not Bar and 31 | [module: Foo, level: :off], # not Foo 32 | ], 33 | format: "DEFAULT $message" # backend specific -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, backends: [] -------------------------------------------------------------------------------- /lib/flex_logger.ex: -------------------------------------------------------------------------------- 1 | defmodule FlexLogger do 2 | @moduledoc """ 3 | `FlexLogger` is a flexible logger (backend) that adds module/application specific log levels to Elixir's `Logger`. 4 | 5 | `FlexLogger` brings the following additions to the table: 6 | 7 | * Configuration of log levels per application, module or even function 8 | 9 | * Possibility of having multiple logger configurations for different applications or modules 10 | 11 | ## Configuration 12 | 13 | `FlexLogger` is configured as a named backend to `Logger`. Following is an example configuration 14 | of a single `FlexLogger` in combination with a :console logger 15 | 16 | config :logger, 17 | backends: [{FlexLogger, :logger_name}] 18 | 19 | config :logger, :logger_name, 20 | logger: :console, 21 | default_level: :debug, # this is the loggers default level 22 | level_config: [ # override default levels 23 | [module: Foo, level: :info] 24 | ], 25 | format: "DEV $message" # backend specific configuration 26 | 27 | The configuration for `FlexLogger` as well as the underlying actual log backend are under the 28 | named config. `FlexLogger` knows the following configuration options: 29 | 30 | * `logger:` The actual logger backend to use. In case of `Logger.Backend.Console` you can also use the :console shortcut. 31 | 32 | * `default_level:` The default log level to use. This should be one of [:off, :debug, :info, :warn, :error]. In addition 33 | to the standard four log levels the :off level allows to turn of logging for either individual modules or if used 34 | as default_level to turn of logging per default to then only enable logging for individual modules or applications 35 | 36 | * `level_config:` A list of log level configurations for modules and applications. Each entry should be a keyword list. 37 | If only a single entry is present the config can be simplified to only a single keyword list like 38 | 39 | level_config: [application: :my_app, level: :info] 40 | 41 | Possible configuration options are `:application`, to match the application, `:module` to match a prefix of a module, 42 | `:function` to match a particular function or `:message` to match a particular message (see below). 43 | The level is set via `:level`. The following configuration 44 | 45 | level_config: [ 46 | [application: :my_app, module: Foo.Bar, level: :debug] 47 | [function: "some_function/1", level: :error] 48 | ] 49 | 50 | would set the log level for any module that starts with `Foo.Bar` in application `:my_app` to :debug. In addition 51 | the log level for any function called `some_function` and that has arity 1 is set to `:error`. Note that if a key 52 | (ie., :application, :module or :function) is not present then it matches anything. 53 | 54 | Via the `:message` key you can define specific log levels based on the content of the logged message. This is 55 | particularly useful in case of filtering out log messages coming from modules that use Erlang's `:error_logger` 56 | in which case no other metadata is available. In case a string is provided for `:message` then `FlexLogger` checks 57 | whether the log message contains the provided string. In case a regular expression is given the log message is matched 58 | against the regular expression. In case a function with arity 1 is provided, the message is passed to that function 59 | which should return a boolean value. Following is an example config that matches the log message against 60 | a regular expression 61 | 62 | level_config: [ 63 | [message: ~r/foo/, level: :debug] 64 | ] 65 | 66 | ### Backend specific configuration 67 | 68 | The entire configuration is passed onto the actual logger for configuration. For example, if you configure 69 | the `LoggerFileBackend` which takes a `path` parmameter you can do this as follows: 70 | 71 | config :logger, 72 | backends: [{FlexLogger, :foo_file_logger}] 73 | 74 | config :logger, :foo_file_logger, 75 | logger: LoggerFileBackend, # The actual backend to use (for example :console or LoggerFileBackend) 76 | default_level: :off, # this is the loggers default level 77 | level_config: [ # override default levels 78 | [module: Foo, level: :info] # available keys are :application, :module, :function 79 | ], 80 | path: "/tmp/foo.log", # backend specific configuration 81 | format: "FOO $message" # backend specific configuration 82 | 83 | 84 | ### Logger Specific Configuration 85 | 86 | `Logger` specific configuration, i.e., not backend specific configuration needs to be specified at the usual place, 87 | for example 88 | 89 | config :logger, 90 | handle_otp_reports: true, 91 | handle_sasl_reports: true 92 | 93 | ## Supported Backends 94 | 95 | `FlexLogger` has been tested with :console and `LoggerFileBackend` but should also work with other logging backends. 96 | 97 | """ 98 | 99 | @behaviour :gen_event 100 | 101 | defmodule State do 102 | @moduledoc false 103 | 104 | defstruct name: nil, logger: nil, logger_state: nil, level: :info, level_config: [], metadata_filter: [] 105 | end 106 | 107 | def init({__MODULE__, name}) do 108 | {:ok, configure(name, [])} 109 | end 110 | 111 | @doc """ 112 | Updates configuration of flex_logger and underlying logger. 113 | Underlying logger may not be changed. 114 | """ 115 | def handle_call({:configure, opts}, %State{name: name} = state) do 116 | {:ok, :ok, configure(name, opts, state)} 117 | end 118 | 119 | def handle_call(_opts, %{logger: nil} = state) do 120 | {:ok, :no_logger, state} 121 | end 122 | 123 | def handle_call(opts, %{logger: logger, logger_state: logger_state} = state) do 124 | # forward to logger 125 | {flag, reply, updated_logger_state} = 126 | logger.handle_call(opts, logger_state) 127 | 128 | {flag, reply, %State{state| logger_state: updated_logger_state}} 129 | end 130 | 131 | def handle_event(_opts, %{logger: nil} = state) do 132 | # ignore, no logger set 133 | {:ok, state} 134 | end 135 | 136 | def handle_event({level, gl, {Logger, msg, ts, md}}, %{logger: logger, logger_state: logger_state} = state) do 137 | if should_log?(md, msg, level, state.level, state.level_config) do 138 | {flag, updated_logger_state} = 139 | logger.handle_event({level, gl, {Logger, msg, ts, md}}, logger_state) 140 | 141 | {flag, %State{state | logger_state: updated_logger_state}} 142 | else 143 | {:ok, state} 144 | end 145 | end 146 | 147 | def handle_event(opts, %{logger: logger, logger_state: logger_state} = state) do 148 | # we forward to logger 149 | {flag, updated_logger_state} = 150 | logger.handle_event(opts, logger_state) 151 | 152 | {flag, %State{state | logger_state: updated_logger_state}} 153 | end 154 | 155 | def handle_info(_opts, %{logger: nil} = state), do: {:ok, state} 156 | 157 | def handle_info(opts, %{logger: logger, logger_state: logger_state} = state) do 158 | {flag, updated_logger_state} = 159 | logger.handle_info(opts, logger_state) 160 | 161 | {flag, %State{state | logger_state: updated_logger_state}} 162 | end 163 | 164 | def handle_info(_, state) do 165 | # ignore 166 | {:ok, state} 167 | end 168 | 169 | def code_change(_old_vsn, state, _extra) do 170 | # ignore 171 | {:ok, state} 172 | end 173 | 174 | def terminate(_reason, _state) do 175 | # ignore 176 | :ok 177 | end 178 | 179 | # helper 180 | 181 | defp should_log?(md, msg, level, default_level, level_config) do 182 | case check_level_configs(md, msg, level, level_config) do 183 | {:match, do_log?} -> do_log? 184 | :no_match -> meet_level?(level, default_level) 185 | end 186 | end 187 | 188 | defp meet_level?(_lvl, nil), do: true 189 | defp meet_level?(_lvl, :off), do: false 190 | defp meet_level?(lvl, min) do 191 | Logger.compare_levels(lvl, min) != :lt 192 | end 193 | 194 | # tests the metadata against the level_config configuration. 195 | # returns 196 | # {:match, false} - in case the config matches and the log call should not be passed on 197 | # {:match, true} - in case the config matches and the log call should be passed on 198 | # {:no_match} - in case no config matches 199 | defp check_level_configs(_md, _msg, _level, nil), do: :no_match 200 | defp check_level_configs(_md, _msg, _level, []), do: :no_match 201 | 202 | defp check_level_configs(md, msg, level, [config | level_configs]) do 203 | case check_module_against_config(md, msg, level, config) do 204 | :no_match -> 205 | check_level_configs(md, msg, level, level_configs) 206 | {:match, level_matches} -> 207 | {:match, level_matches} 208 | end 209 | end 210 | 211 | defp check_module_against_config(md, msg, level, config) do 212 | app = Keyword.get(md, :application, nil) 213 | module = Keyword.get(md, :module, nil) 214 | function = Keyword.get(md, :function, nil) 215 | 216 | allowed_app = Keyword.get(config, :application, nil) 217 | allowed_module = Keyword.get(config, :module, nil) 218 | allowed_function = Keyword.get(config, :function, nil) 219 | msg_matcher = Keyword.get(config, :message, nil) 220 | 221 | if (not matches?(app, allowed_app) or 222 | not matches_prefix?(module, allowed_module) or 223 | not matches?(function, allowed_function) or 224 | not message_matches?(msg, msg_matcher)) do 225 | :no_match 226 | else 227 | min_level = Keyword.get(config, :level, :debug) 228 | {:match, meet_level?(level, min_level)} 229 | end 230 | end 231 | 232 | defp matches?(_, nil), do: true 233 | defp matches?(nil, _), do: false 234 | defp matches?(a, b), do: a == b 235 | 236 | defp matches_prefix?(_, nil), do: true 237 | defp matches_prefix?(nil, _), do: false 238 | defp matches_prefix?(module, module_prefix) when is_atom(module) do 239 | matches_prefix?(Atom.to_string(module), module_prefix) 240 | end 241 | defp matches_prefix?(module, module_prefix) when is_atom(module_prefix) do 242 | matches_prefix?(module, Atom.to_string(module_prefix)) 243 | end 244 | defp matches_prefix?(module, module_prefix) do 245 | String.starts_with?(module, module_prefix) 246 | end 247 | 248 | defp message_matches?(_, nil), do: true 249 | defp message_matches?(cl, msg_matcher) when is_list(cl) do 250 | message_matches?(to_string(cl), msg_matcher) 251 | end 252 | defp message_matches?(msg, msg_matcher) when is_binary(msg_matcher) do 253 | String.contains?(msg, msg_matcher) 254 | end 255 | defp message_matches?(msg, %Regex{}=msg_matcher) do 256 | Regex.match?(msg_matcher, msg) 257 | end 258 | defp message_matches?(msg, msg_matcher) when is_function(msg_matcher) do 259 | msg_matcher.(msg) 260 | end 261 | 262 | defp configure(name, opts), do: configure(name, opts, %State{}) 263 | 264 | defp configure(name, opts, %State{} = state) do 265 | env = Application.get_env(:logger, name, []) 266 | opts = Keyword.merge(env, opts) 267 | 268 | old_logger = state.logger 269 | logger = translate_logger(Keyword.get(opts, :logger, nil)) 270 | 271 | logger_state = cond do 272 | is_nil(logger) -> 273 | nil 274 | old_logger == logger -> 275 | update_logger_config(logger, opts, state.logger_state) 276 | true -> 277 | {:ok, logger_state} = init_logger(logger, name) 278 | update_logger_config(logger, opts, logger_state) 279 | end 280 | 281 | %State{state | 282 | name: name, 283 | logger: logger, 284 | logger_state: logger_state, 285 | level: Keyword.get(opts, :default_level, :debug), 286 | level_config: clean_level_config(Keyword.get(opts, :level_config, [])), 287 | } 288 | end 289 | 290 | defp update_logger_config(logger, opts, logger_state) do 291 | {:ok, :ok, updated_logger_state} = logger.handle_call({:configure, opts}, logger_state) 292 | updated_logger_state 293 | end 294 | 295 | defp clean_level_config([]), do: [] 296 | defp clean_level_config(cnf) do 297 | if Keyword.keyword?(cnf) do 298 | [cnf] 299 | else 300 | cnf 301 | end 302 | end 303 | 304 | defp translate_logger(:console), do: Logger.Backends.Console 305 | defp translate_logger(logger), do: logger 306 | 307 | defp init_logger(nil), do: nil 308 | defp init_logger(Logger.Backends.Console), do: Logger.Backends.Console.init(:console) 309 | defp init_logger(logger), do: logger.init(logger) 310 | 311 | defp init_logger(logger, name) do 312 | try do 313 | logger.init({logger, name}) 314 | rescue 315 | _ -> init_logger(logger) 316 | end 317 | end 318 | 319 | end 320 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule FlexLogger.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :flex_logger, 7 | source_url: "https://github.com/arnomi/elixir-flex-logger", 8 | docs: [main: FlexLogger], 9 | package: package(), 10 | version: "0.2.1", 11 | description: description(), 12 | elixir: "~> 1.5", 13 | elixirc_paths: elixirc_paths(Mix.env), 14 | start_permanent: Mix.env == :prod, 15 | deps: deps() 16 | ] 17 | end 18 | 19 | # Run "mix help compile.app" to learn about applications. 20 | def application do 21 | [ 22 | extra_applications: [:logger] 23 | ] 24 | end 25 | 26 | defp elixirc_paths(:test), do: elixirc_paths() ++ ["test/support"] 27 | defp elixirc_paths(_), do: elixirc_paths() 28 | defp elixirc_paths(), do: ["lib"] 29 | 30 | # Type `mix help deps` for more examples and options 31 | defp deps do 32 | [ {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, 33 | {:credo, "~> 0.8", only: [:dev, :test], runtime: false}, 34 | {:logger_file_backend, "~> 0.0.10", only: :dev}] 35 | end 36 | 37 | defp description() do 38 | "FlexLogger adds module/application specific log levels to Elixir's Logger." 39 | end 40 | 41 | defp package() do 42 | [ 43 | files: ["lib", "mix.exs", "README*", "LICENSE*", "CHANGELOG.*"], 44 | maintainers: ["Arno Mittelbach"], 45 | licenses: ["MIT"], 46 | links: %{"GitHub" => "https://github.com/arnomi/elixir-flex-logger"} 47 | ] 48 | end 49 | end -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"}, 2 | "credo": {:hex, :credo, "0.8.8", "990e7844a8d06ebacd88744a55853a83b74270b8a8461c55a4d0334b8e1736c9", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [], [], "hexpm"}, 4 | "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "logger_file_backend": {:hex, :logger_file_backend, "0.0.10", "876f9f84ae110781207c54321ffbb62bebe02946fe3c13f0d7c5f5d8ad4fa910", [], [], "hexpm"}} 6 | -------------------------------------------------------------------------------- /test/flex_logger_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FlexLoggerTest do 2 | use ExUnit.Case, async: false 3 | doctest FlexLogger 4 | 5 | require Logger 6 | 7 | @backend {FlexLogger, :test} 8 | Logger.add_backend @backend 9 | 10 | test "does not crash on empty logger" do 11 | config logger: nil 12 | debug "empty logger test" 13 | :ok 14 | end 15 | 16 | test "works with console logger with full name" do 17 | config [logger: Logger.Backends.Console, default_level: :debug] 18 | debug "console logger test" 19 | :ok 20 | end 21 | 22 | test "works with :console logger" do 23 | config [logger: :console, default_level: :debug] 24 | debug "console logger test" 25 | :ok 26 | end 27 | 28 | test "works with named logger" do 29 | config [logger: LoggerMockWithName, default_level: :debug] 30 | reset() 31 | debug "test message" 32 | assert %{:events => [debug: "test message"]} = mock_state() 33 | end 34 | 35 | test "default_level filters" do 36 | config [logger: LoggerMockWithName, default_level: :warn] 37 | reset() 38 | debug "test message" 39 | assert %{:events => []} = mock_state() 40 | end 41 | 42 | test "default_level includes" do 43 | config [logger: LoggerMockWithName, default_level: :warn] 44 | reset() 45 | warn "test message" 46 | assert %{:events => [warn: "test message"]} = mock_state() 47 | end 48 | 49 | test "works with unnamed logger" do 50 | config [logger: LoggerMockWithoutName, default_level: :debug] 51 | reset() 52 | debug "test message" 53 | assert %{:events => [debug: "test message"]} = mock_state() 54 | end 55 | 56 | test "take default level if no specific rule matches" do 57 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [module: FooBar, level: :info]] 58 | reset() 59 | debug "test message" 60 | assert %{:events => [debug: "test message"]} = mock_state() 61 | end 62 | 63 | test "turn off logging via :off" do 64 | config [logger: LoggerMockWithoutName, default_level: :off] 65 | reset() 66 | debug "test message" 67 | assert %{:events => []} = mock_state() 68 | end 69 | 70 | test "override :off" do 71 | config [logger: LoggerMockWithoutName, default_level: :off, level_config: [module: A, level: :info]] 72 | reset() 73 | A.info "test message" 74 | assert %{:events => [info: "test message"]} = mock_state() 75 | end 76 | 77 | test "take specific rule overrides default level" do 78 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [module: A, level: :info]] 79 | reset() 80 | A.debug "test message" 81 | assert %{:events => []} = mock_state() 82 | end 83 | 84 | test "take module prefix" do 85 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [module: Foo, level: :info]] 86 | reset() 87 | Foo.Bar.debug "test message" 88 | Foo.Bar.info "info" 89 | assert %{:events => [info: "info"]} = mock_state() 90 | end 91 | 92 | test "test application" do 93 | config [logger: LoggerMockWithoutName, default_level: :info, level_config: [application: :flex_logger, level: :debug]] 94 | reset() 95 | A.debug "test message" 96 | assert %{:events => [debug: "test message"]} = mock_state() 97 | end 98 | 99 | test "test override function" do 100 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [ 101 | [module: Foo.Bar, function: "debug/1", level: :debug], 102 | [module: Foo, level: :info]]] 103 | reset() 104 | Foo.Bar.debug "test message" 105 | assert %{:events => [debug: "test message"]} = mock_state() 106 | end 107 | 108 | test "test function arity" do 109 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [ 110 | [module: Foo.Bar, function: "debug/2", level: :debug], 111 | [module: Foo, level: :info]]] 112 | reset() 113 | assert %{:events => []} = mock_state() 114 | end 115 | 116 | test "test order matters" do 117 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [[module: Foo, level: :info], 118 | [module: Foo.Bar, function: "debug/1", level: :debug]]] 119 | reset() 120 | Foo.Bar.debug "test message" 121 | assert %{:events => []} = mock_state() 122 | end 123 | 124 | test "multiple rules" do 125 | config [logger: LoggerMockWithoutName, default_level: :debug, level_config: [[module: Foo, level: :info], [module: A, level: :warn]]] 126 | reset() 127 | 128 | Foo.Bar.debug "test message" 129 | A.debug "test message" 130 | A.warn "warn" 131 | 132 | assert %{:events => [warn: "warn"]} = mock_state() 133 | end 134 | 135 | test "message contains" do 136 | config [logger: LoggerMockWithoutName, default_level: :error, level_config: [[message: "foo", level: :debug]]] 137 | reset() 138 | 139 | A.warn "foo warn" 140 | A.warn "bar warn" 141 | 142 | assert %{:events => [warn: "foo warn"]} = mock_state() 143 | end 144 | 145 | test "message as charlist contains" do 146 | config [logger: LoggerMockWithoutName, default_level: :error, level_config: [[message: "foo", level: :debug]]] 147 | reset() 148 | 149 | A.warn 'foo warn' 150 | A.warn 'bar warn' 151 | 152 | assert %{:events => [warn: 'foo warn']} = mock_state() 153 | end 154 | 155 | test "message regex" do 156 | config [logger: LoggerMockWithoutName, default_level: :error, level_config: [[message: ~r/bar/, level: :debug]]] 157 | reset() 158 | 159 | A.warn "foo warn" 160 | A.warn "bar warn" 161 | 162 | assert %{:events => [warn: "bar warn"]} = mock_state() 163 | end 164 | 165 | test "message regex with charlist" do 166 | config [logger: LoggerMockWithoutName, default_level: :error, level_config: [[message: ~r/bar/, level: :debug]]] 167 | reset() 168 | 169 | A.warn 'foo warn' 170 | A.warn 'bar warn' 171 | 172 | assert %{:events => [warn: 'bar warn']} = mock_state() 173 | end 174 | 175 | test "message function" do 176 | config [logger: LoggerMockWithoutName, default_level: :error, level_config: [[message: fn msg -> String.contains?(msg, "bar") end, level: :debug]]] 177 | reset() 178 | 179 | A.warn "foo warn" 180 | A.warn "bar warn" 181 | 182 | assert %{:events => [warn: "bar warn"]} = mock_state() 183 | end 184 | 185 | test "can set config directly" do 186 | config [logger: LoggerMockWithoutName] 187 | reset() 188 | config [logger: LoggerMockWithoutName, default_level: :debug, foo: :bar] 189 | 190 | assert %{:configure => [logger: LoggerMockWithoutName, default_level: :debug, foo: :bar]} = mock_state() 191 | end 192 | 193 | defp debug(msg) do 194 | Logger.debug msg 195 | Logger.flush() 196 | end 197 | 198 | defp warn(msg) do 199 | Logger.warn msg 200 | Logger.flush() 201 | end 202 | 203 | defp mock_state do 204 | :gen_event.call(Logger, @backend, :get_state) 205 | end 206 | 207 | defp reset do 208 | :gen_event.call(Logger, @backend, :reset) 209 | end 210 | 211 | defp config(opts) do 212 | Logger.configure_backend(@backend, opts) 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /test/support/a.ex: -------------------------------------------------------------------------------- 1 | defmodule A do 2 | @moduledoc false 3 | 4 | require Logger 5 | 6 | def info(msg) do 7 | Logger.info(msg) 8 | end 9 | 10 | def debug(msg) do 11 | Logger.debug(msg) 12 | end 13 | 14 | def warn(msg) do 15 | Logger.warn(msg) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/support/foo/bar.ex: -------------------------------------------------------------------------------- 1 | defmodule Foo.Bar do 2 | @moduledoc false 3 | 4 | require Logger 5 | 6 | def info(msg) do 7 | Logger.info(msg) 8 | end 9 | 10 | def debug(msg) do 11 | Logger.debug(msg) 12 | end 13 | 14 | def warn(msg) do 15 | Logger.warn(msg) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/support/logger_mock_with_name.ex: -------------------------------------------------------------------------------- 1 | defmodule LoggerMockWithName do 2 | @moduledoc false 3 | 4 | def init({__MODULE__, name}) do 5 | {:ok, %{:events => [], :name => name, :configure => nil}} 6 | end 7 | 8 | def handle_call({:configure, opts}, state) do 9 | {:ok, :ok, %{state | :configure => opts}} 10 | end 11 | 12 | def handle_call(:reset, state) do 13 | {:ok, :ok, %{:events => [], :name => state[:name], :configure => nil}} 14 | end 15 | 16 | def handle_call(:get_state, state) do 17 | {:ok, state, state} 18 | end 19 | 20 | def handle_event({level, _gl, {Logger, msg, _ts, _md}}, state) do 21 | {:ok, %{state | :events => state[:events] ++ [{level, msg}]}} 22 | end 23 | 24 | def handle_event(_, state) do 25 | {:ok, state} 26 | end 27 | 28 | def handle_info(_opts, state), do: {:ok, state} 29 | 30 | end -------------------------------------------------------------------------------- /test/support/logger_mock_without_name.ex: -------------------------------------------------------------------------------- 1 | defmodule LoggerMockWithoutName do 2 | @moduledoc false 3 | 4 | def init(__MODULE__) do 5 | {:ok, %{:events => [], :configure => nil}} 6 | end 7 | 8 | def handle_call({:configure, opts}, state) do 9 | {:ok, :ok, %{state | :configure => opts}} 10 | end 11 | 12 | def handle_call(:reset, _state) do 13 | {:ok, :ok, %{:events => [], :configure => nil}} 14 | end 15 | 16 | def handle_call(:get_state, state) do 17 | {:ok, state, state} 18 | end 19 | 20 | def handle_event({level, _gl, {Logger, msg, _ts, _md}}, state) do 21 | {:ok, %{state | :events => state[:events] ++ [{level, msg}]}} 22 | end 23 | 24 | def handle_event(_, state) do 25 | {:ok, state} 26 | end 27 | 28 | def handle_info(_opts, state), do: {:ok, state} 29 | 30 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | :application.start :logger 2 | ExUnit.start() 3 | --------------------------------------------------------------------------------