logger.ex (4323B)
1 defmodule Absinthe.Logger do 2 @default_log true 3 @default_filter_variables ~w(token password) 4 @default_pipeline false 5 6 @moduledoc """ 7 Handles logging of Absinthe-specific events. 8 9 ## Variable filtering 10 11 Absinthe can filter out sensitive information like tokens and passwords 12 during logging. They are replaced by `"[FILTERED]"`. 13 14 Use the `:filter_variables` configuration setting for this module. 15 For example: 16 17 config :absinthe, Absinthe.Logger, 18 filter_variables: ["token", "password", "secret"] 19 20 With the configuration above, Absinthe will filter any variable whose name 21 includes the terms `token`, `password`, or `secret`. The match is case 22 sensitive. 23 24 Note that filtering only applies to GraphQL variables - the query itself is 25 logged before any parsing happens. 26 27 The default is `#{inspect(@default_filter_variables)}`. 28 29 ## Pipeline display 30 31 Absinthe can optionally display the list of pipeline phases for each processed 32 document when logging. To enable this feature, set the `:pipeline` 33 configuration option for this module: 34 35 config :absinthe, Absinthe.Logger, 36 pipeline: true 37 38 The default is `#{inspect(@default_pipeline)}`. 39 40 ## Disabling 41 42 To disable Absinthe logging, set the `:log` configuration option to `false`: 43 44 config :absinthe, 45 log: false 46 47 The default is `#{inspect(@default_log)}`. 48 49 """ 50 require Logger 51 52 @doc """ 53 Log a document being processed. 54 """ 55 @spec log_run( 56 level :: Logger.level(), 57 {doc :: Absinthe.Pipeline.data_t(), schema :: Absinthe.Schema.t(), 58 pipeline :: Absinthe.Pipeline.t(), opts :: Keyword.t()} 59 ) :: :ok 60 def log_run(level, {doc, schema, pipeline, opts}) do 61 if Application.get_env(:absinthe, :log, @default_log) do 62 Logger.log(level, fn -> 63 [ 64 "ABSINTHE", 65 " schema=", 66 inspect(schema), 67 " variables=", 68 variables_body(opts), 69 pipeline_section(pipeline), 70 "---", 71 ?\n, 72 document(doc), 73 ?\n, 74 "---" 75 ] 76 end) 77 end 78 79 :ok 80 end 81 82 @doc false 83 @spec document(Absinthe.Pipeline.data_t()) :: binary 84 def document(value) when value in ["", nil] do 85 "[EMPTY]" 86 end 87 88 def document(%Absinthe.Blueprint{name: nil}) do 89 "[COMPILED]" 90 end 91 92 def document(%Absinthe.Blueprint{name: name}) do 93 "[COMPILED#<#{name}>]" 94 end 95 96 def document(%Absinthe.Language.Source{body: body}) do 97 document(body) 98 end 99 100 def document(document) when is_binary(document) do 101 String.trim(document) 102 end 103 104 def document(other) do 105 inspect(other) 106 end 107 108 @doc false 109 @spec filter_variables(map) :: map 110 @spec filter_variables(map, [String.t()]) :: map 111 def filter_variables(data, filter_variables \\ variables_to_filter()) 112 113 def filter_variables(%{__struct__: mod} = struct, _filter_variables) when is_atom(mod) do 114 struct 115 end 116 117 def filter_variables(%{} = map, filter_variables) do 118 Enum.into(map, %{}, fn {k, v} -> 119 if is_binary(k) and String.contains?(k, filter_variables) do 120 {k, "[FILTERED]"} 121 else 122 {k, filter_variables(v, filter_variables)} 123 end 124 end) 125 end 126 127 def filter_variables([_ | _] = list, filter_variables) do 128 Enum.map(list, &filter_variables(&1, filter_variables)) 129 end 130 131 def filter_variables(other, _filter_variables), do: other 132 133 @spec variables_to_filter() :: [String.t()] 134 defp variables_to_filter do 135 Application.get_env(:absinthe, __MODULE__, []) 136 |> Keyword.get(:filter_variables, @default_filter_variables) 137 end 138 139 @spec variables_body(Keyword.t()) :: String.t() 140 defp variables_body(opts) do 141 Keyword.get(opts, :variables, %{}) 142 |> filter_variables() 143 |> inspect() 144 end 145 146 @spec pipeline_section(Absinthe.Pipeline.t()) :: iolist 147 defp pipeline_section(pipeline) do 148 Application.get_env(:absinthe, __MODULE__, []) 149 |> Keyword.get(:pipeline, @default_pipeline) 150 |> case do 151 true -> 152 do_pipeline_section(pipeline) 153 154 false -> 155 ?\n 156 end 157 end 158 159 @spec do_pipeline_section(Absinthe.Pipeline.t()) :: iolist 160 defp do_pipeline_section(pipeline) do 161 [ 162 " pipeline=", 163 pipeline 164 |> Enum.map(fn 165 {mod, _} -> mod 166 mod -> mod 167 end) 168 |> inspect, 169 ?\n 170 ] 171 end 172 end