1defmodule Logger.Translator do 2 @moduledoc """ 3 Default translation for Erlang log messages. 4 5 Logger allows developers to rewrite log messages provided by 6 OTP applications into a format more compatible with Elixir 7 log messages by providing a translator. 8 9 A translator is simply a tuple containing a module and a function 10 that can be added and removed via the `Logger.add_translator/1` and 11 `Logger.remove_translator/1` functions and is invoked for every Erlang 12 message above the minimum log level with four arguments: 13 14 * `min_level` - the current Logger level 15 * `level` - the level of the message being translated 16 * `kind` - if the message is a `:report` or `:format` 17 * `message` - the message to format. If it is `:report`, it is a tuple 18 with `{report_type, report_data}`, if it is `:format`, it is a 19 tuple with `{format_message, format_args}`. 20 21 The function must return: 22 23 * `{:ok, chardata, metadata}` - if the message translation with its metadata 24 * `{:ok, chardata}` - the translated message 25 * `:skip` - if the message is not meant to be translated nor logged 26 * `:none` - if there is no translation, which triggers the next translator 27 28 See the function `translate/4` in this module for an example implementation 29 and the default messages translated by Logger. 30 """ 31 32 @doc """ 33 Built-in translation function. 34 """ 35 def translate(min_level, level, kind, message) 36 37 def translate(min_level, _level, :report, {:logger, %{label: label} = report}) do 38 case label do 39 {:gen_server, :terminate} -> 40 report_gen_server_terminate(min_level, report) 41 42 {:gen_event, :terminate} -> 43 report_gen_event_terminate(min_level, report) 44 45 _ -> 46 :skip 47 end 48 end 49 50 def translate(min_level, _level, :report, {{:proc_lib, :crash}, data}) do 51 report_crash(min_level, data) 52 end 53 54 def translate(min_level, _level, :report, {{:supervisor, :progress}, data}) do 55 report_supervisor_progress(min_level, data) 56 end 57 58 def translate(min_level, _level, :report, {{:supervisor, _}, data}) do 59 report_supervisor(min_level, data) 60 end 61 62 def translate( 63 _min_level, 64 _level, 65 :report, 66 {{:application_controller, :progress}, [application: app, started_at: node]} 67 ) do 68 {:ok, ["Application ", Atom.to_string(app), " started at " | inspect(node)]} 69 end 70 71 def translate( 72 _min_level, 73 _level, 74 :report, 75 {{:application_controller, :exit}, [application: app, exited: reason, type: _type]} 76 ) do 77 {:ok, ["Application ", Atom.to_string(app), " exited: " | Application.format_error(reason)]} 78 end 79 80 def translate( 81 _min_level, 82 :error, 83 :report, 84 {{Task.Supervisor, :terminating}, 85 %{ 86 name: name, 87 starter: starter, 88 function: function, 89 args: args, 90 reason: reason 91 }} 92 ) do 93 opts = Application.get_env(:logger, :translator_inspect_opts) 94 95 {formatted, reason} = format_reason(reason) 96 metadata = [crash_reason: reason] ++ registered_name(name) 97 98 msg = 99 ["Task #{inspect(name)} started from #{inspect(starter)} terminating"] ++ 100 [formatted, "\nFunction: #{inspect(function, opts)}"] ++ 101 ["\n Args: #{inspect(args, opts)}"] 102 103 {:ok, msg, metadata} 104 end 105 106 def translate(min_level, :error, :format, message) do 107 case message do 108 # This is no longer emitted by Erlang/OTP but it may be 109 # manually emitted by libraries like connection. 110 {'** Generic server ' ++ _, [name, last, state, reason | client]} -> 111 opts = Application.fetch_env!(:logger, :translator_inspect_opts) 112 {formatted, reason} = format_reason(reason) 113 metadata = [crash_reason: reason] ++ registered_name(name) 114 115 msg = 116 ["GenServer #{inspect(name)} terminating", formatted] ++ 117 ["\nLast message#{format_from(client)}: #{inspect(last, opts)}"] 118 119 if min_level == :debug do 120 msg = [msg, "\nState: #{inspect(state, opts)}" | format_client(client)] 121 {:ok, msg, metadata} 122 else 123 {:ok, msg, metadata} 124 end 125 126 {'Error in process ' ++ _, [pid, node, {reason, stack}]} -> 127 reason = Exception.normalize(:error, reason, stack) 128 129 msg = [ 130 "Process ", 131 inspect(pid), 132 " on node ", 133 inspect(node), 134 " raised an exception" | format(:error, reason, stack) 135 ] 136 137 {:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]} 138 139 {'Error in process ' ++ _, [pid, {reason, stack}]} -> 140 reason = Exception.normalize(:error, reason, stack) 141 msg = ["Process ", inspect(pid), " raised an exception" | format(:error, reason, stack)] 142 {:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]} 143 144 _ -> 145 :none 146 end 147 end 148 149 def translate(_min_level, :info, :report, { 150 :std_info, 151 [application: app, exited: reason, type: _type] 152 }) do 153 {:ok, ["Application ", Atom.to_string(app), " exited: " | Application.format_error(reason)]} 154 end 155 156 def translate(min_level, :error, :report, {{:error_logger, :error_report}, data}) do 157 report_supervisor(min_level, data) 158 end 159 160 def translate(min_level, :error, :report, {:supervisor_report, data}) do 161 report_supervisor(min_level, data) 162 end 163 164 def translate(min_level, :error, :report, {:crash_report, data}) do 165 report_crash(min_level, data) 166 end 167 168 def translate(min_level, :info, :report, {:progress, [{:supervisor, _} | _] = data}) do 169 report_supervisor_progress(min_level, data) 170 end 171 172 def translate(_min_level, :info, :report, {:progress, [application: app, started_at: node]}) do 173 {:ok, ["Application ", Atom.to_string(app), " started at " | inspect(node)]} 174 end 175 176 ## Helpers 177 178 def translate(_min_level, _level, _kind, _message) do 179 :none 180 end 181 182 defp report_gen_server_terminate(min_level, report) do 183 inspect_opts = Application.fetch_env!(:logger, :translator_inspect_opts) 184 185 %{ 186 client_info: client, 187 last_message: last, 188 name: name, 189 reason: reason, 190 state: state 191 } = report 192 193 {formatted, reason} = format_reason(reason) 194 metadata = [crash_reason: reason] ++ registered_name(name) 195 196 msg = 197 ["GenServer ", inspect(name), " terminating", formatted] ++ 198 ["\nLast message", format_last_message_from(client), ": ", inspect(last, inspect_opts)] 199 200 if min_level == :debug do 201 msg = [msg, "\nState: ", inspect(state, inspect_opts) | format_client_info(client)] 202 {:ok, msg, metadata} 203 else 204 {:ok, msg, metadata} 205 end 206 end 207 208 defp report_gen_event_terminate(min_level, report) do 209 inspect_opts = Application.fetch_env!(:logger, :translator_inspect_opts) 210 211 %{ 212 handler: handler, 213 last_message: last, 214 name: name, 215 reason: reason, 216 state: state 217 } = report 218 219 reason = 220 case reason do 221 {:EXIT, why} -> why 222 _ -> reason 223 end 224 225 {formatted, reason} = format_reason(reason) 226 metadata = [crash_reason: reason] ++ registered_name(name) 227 228 msg = 229 [":gen_event handler ", inspect(handler), " installed in ", inspect(name), " terminating"] ++ 230 [formatted, "\nLast message: ", inspect(last, inspect_opts)] 231 232 if min_level == :debug do 233 {:ok, [msg, "\nState: ", inspect(state, inspect_opts)], metadata} 234 else 235 {:ok, msg, metadata} 236 end 237 end 238 239 defp report_supervisor_progress( 240 min_level, 241 supervisor: sup, 242 started: [{:pid, pid}, {:id, id} | started] 243 ) do 244 msg = 245 ["Child ", inspect(id), " of Supervisor ", sup_name(sup), " started"] ++ 246 ["\nPid: ", inspect(pid)] ++ child_info(min_level, started) 247 248 {:ok, msg} 249 end 250 251 defp report_supervisor_progress( 252 min_level, 253 supervisor: sup, 254 started: [{:pid, pid} | started] 255 ) do 256 msg = 257 ["Child of Supervisor ", sup_name(sup), " started", "\nPid: ", inspect(pid)] ++ 258 child_info(min_level, started) 259 260 {:ok, msg} 261 end 262 263 defp report_supervisor_progress(_min_level, _other), do: :none 264 265 defp report_supervisor( 266 min_level, 267 supervisor: sup, 268 errorContext: context, 269 reason: reason, 270 offender: [{:pid, pid}, {:id, id} | offender] 271 ) do 272 pid_info = 273 if is_pid(pid) and context != :shutdown do 274 ["\nPid: ", inspect(pid)] 275 else 276 [] 277 end 278 279 msg = 280 ["Child ", inspect(id), " of Supervisor ", sup_name(sup)] ++ 281 [?\s, sup_context(context), "\n** (exit) ", offender_reason(reason, context)] ++ 282 pid_info ++ child_info(min_level, offender) 283 284 {:ok, msg} 285 end 286 287 defp report_supervisor( 288 min_level, 289 supervisor: sup, 290 errorContext: context, 291 reason: reason, 292 offender: [{:nb_children, n}, {:id, id} | offender] 293 ) do 294 msg = 295 ["Children ", inspect(id), " of Supervisor ", sup_name(sup), ?\s, sup_context(context)] ++ 296 ["\n** (exit) ", offender_reason(reason, context), "\nNumber: ", Integer.to_string(n)] ++ 297 child_info(min_level, offender) 298 299 {:ok, msg} 300 end 301 302 defp report_supervisor( 303 min_level, 304 supervisor: sup, 305 errorContext: context, 306 reason: reason, 307 offender: [{:pid, pid} | offender] 308 ) do 309 msg = 310 ["Child of Supervisor ", sup_name(sup), ?\s, sup_context(context)] ++ 311 ["\n** (exit) ", offender_reason(reason, context), "\nPid: ", inspect(pid)] ++ 312 child_info(min_level, offender) 313 314 {:ok, msg} 315 end 316 317 defp report_supervisor(_min_level, _other), do: :none 318 319 # If start call raises reason will be of form {:EXIT, reason} 320 defp offender_reason({:EXIT, reason}, :start_error) do 321 Exception.format_exit(reason) 322 end 323 324 defp offender_reason(reason, _context) do 325 Exception.format_exit(reason) 326 end 327 328 defp sup_name({:local, name}), do: inspect(name) 329 defp sup_name({:global, name}), do: inspect(name) 330 defp sup_name({:via, _mod, name}), do: inspect(name) 331 defp sup_name({pid, mod}), do: [inspect(pid), " (", inspect(mod), ?)] 332 defp sup_name(unknown_name), do: inspect(unknown_name) 333 334 defp sup_context(:start_error), do: "failed to start" 335 defp sup_context(:child_terminated), do: "terminated" 336 defp sup_context(:shutdown), do: "caused shutdown" 337 defp sup_context(:shutdown_error), do: "shut down abnormally" 338 339 defp child_info(min_level, [{:mfargs, {mod, fun, args}} | debug]) do 340 ["\nStart Call: ", format_mfa(mod, fun, args) | child_debug(min_level, debug)] 341 end 342 343 # Comes from bridge with MFA 344 defp child_info(min_level, [{:mfa, {mod, fun, args}} | debug]) do 345 ["\nStart Call: ", format_mfa(mod, fun, args) | child_debug(min_level, debug)] 346 end 347 348 # Comes from bridge with Mod 349 defp child_info(min_level, [{:mod, mod} | debug]) do 350 ["\nStart Module: ", inspect(mod) | child_debug(min_level, debug)] 351 end 352 353 defp child_info(_min_level, _child) do 354 [] 355 end 356 357 defp child_debug(:debug, opts) do 358 for {key, value} <- opts do 359 child_debug_key(key, value) 360 end 361 end 362 363 defp child_debug(_min_level, _child) do 364 [] 365 end 366 367 defp child_debug_key(:restart_type, value), do: ["\nRestart: " | inspect(value)] 368 defp child_debug_key(:shutdown, value), do: ["\nShutdown: " | inspect(value)] 369 defp child_debug_key(:child_type, value), do: ["\nType: " | inspect(value)] 370 defp child_debug_key(:significant, value), do: if(value, do: "\nSignificant: true", else: []) 371 defp child_debug_key(_, _), do: [] 372 373 defp report_crash(min_level, [[{:initial_call, initial_call} | crashed], linked]) do 374 mfa = initial_call_to_mfa(initial_call) 375 report_crash(min_level, crashed, [{:initial_call, mfa}], linked) 376 end 377 378 defp report_crash(min_level, [crashed, linked]) do 379 report_crash(min_level, crashed, [], linked) 380 end 381 382 defp report_crash(min_level, crashed, extra, linked) do 383 [ 384 {:pid, pid}, 385 {:registered_name, name}, 386 {:error_info, {kind, reason, stack}} | crashed 387 ] = crashed 388 389 dictionary = crashed[:dictionary] 390 reason = Exception.normalize(kind, reason, stack) 391 392 case Keyword.get(dictionary, :logger_enabled, true) do 393 false -> 394 :skip 395 396 true -> 397 user_metadata = Keyword.get(dictionary, :"$logger_metadata$", %{}) |> Map.to_list() 398 399 msg = 400 ["Process ", crash_name(pid, name), " terminating", format(kind, reason, stack)] ++ 401 [crash_info(min_level, extra ++ crashed, [?\n]), crash_linked(min_level, linked)] 402 403 extra = 404 if ancestors = crashed[:ancestors], do: [{:ancestors, ancestors} | extra], else: extra 405 406 extra = 407 if callers = dictionary[:"$callers"], do: [{:callers, callers} | extra], else: extra 408 409 extra = [{:crash_reason, exit_reason(kind, reason, stack)} | extra] 410 {:ok, msg, registered_name(name) ++ extra ++ user_metadata} 411 end 412 end 413 414 defp initial_call_to_mfa({:supervisor, module, _}), do: {module, :init, 1} 415 defp initial_call_to_mfa({:supervisor_bridge, module, _}), do: {module, :init, 1} 416 defp initial_call_to_mfa({mod, fun, args}) when is_list(args), do: {mod, fun, length(args)} 417 defp initial_call_to_mfa(mfa), do: mfa 418 419 defp crash_name(pid, []), do: inspect(pid) 420 defp crash_name(pid, name), do: [inspect(name), " (", inspect(pid), ?)] 421 422 defp crash_info(min_level, [{:initial_call, {mod, fun, args}} | info], prefix) do 423 [prefix, "Initial Call: ", crash_call(mod, fun, args) | crash_info(min_level, info, prefix)] 424 end 425 426 defp crash_info(min_level, [{:current_function, {mod, fun, args}} | info], prefix) do 427 [prefix, "Current Call: ", crash_call(mod, fun, args) | crash_info(min_level, info, prefix)] 428 end 429 430 defp crash_info(min_level, [{:current_function, []} | info], prefix) do 431 crash_info(min_level, info, prefix) 432 end 433 434 defp crash_info(min_level, [{:ancestors, ancestors} | debug], prefix) do 435 [prefix, "Ancestors: ", inspect(ancestors) | crash_info(min_level, debug, prefix)] 436 end 437 438 defp crash_info(:debug, debug, prefix) do 439 for {key, value} <- debug do 440 crash_debug(key, value, prefix) 441 end 442 end 443 444 defp crash_info(_, _, _) do 445 [] 446 end 447 448 defp crash_call(mod, fun, arity) when is_integer(arity) do 449 format_mfa(mod, fun, arity) 450 end 451 452 defp crash_call(mod, fun, args) do 453 format_mfa(mod, fun, length(args)) 454 end 455 456 defp crash_debug(:current_stacktrace, stack, prefix) do 457 stack_prefix = [prefix | " "] 458 stacktrace = Enum.map(stack, &[stack_prefix | Exception.format_stacktrace_entry(&1)]) 459 [prefix, "Current Stacktrace:" | stacktrace] 460 end 461 462 defp crash_debug(key, value, prefix) do 463 [prefix, crash_debug_key(key), ?:, ?\s, inspect(value)] 464 end 465 466 defp crash_debug_key(key) do 467 case key do 468 :message_queue_len -> "Message Queue Length" 469 :messages -> "Messages" 470 :links -> "Links" 471 :dictionary -> "Dictionary" 472 :trap_exit -> "Trapping Exits" 473 :status -> "Status" 474 :heap_size -> "Heap Size" 475 :stack_size -> "Stack Size" 476 :reductions -> "Reductions" 477 end 478 end 479 480 defp crash_linked(_min_level, []), do: [] 481 482 defp crash_linked(min_level, neighbours) do 483 Enum.reduce(neighbours, "\nNeighbours:", fn {:neighbour, info}, acc -> 484 [acc | crash_neighbour(min_level, info)] 485 end) 486 end 487 488 @indent " " 489 490 defp crash_neighbour(min_level, [{:pid, pid}, {:registered_name, []} | info]) do 491 [?\n, @indent, inspect(pid) | crash_info(min_level, info, [?\n, @indent | @indent])] 492 end 493 494 defp crash_neighbour(min_level, [{:pid, pid}, {:registered_name, name} | info]) do 495 [?\n, @indent, inspect(name), " (", inspect(pid), ")"] ++ 496 crash_info(min_level, info, [?\n, @indent | @indent]) 497 end 498 499 defp format_last_message_from({_, {name, _}}), do: [" (from ", inspect(name), ")"] 500 defp format_last_message_from({from, _}), do: [" (from ", inspect(from), ")"] 501 defp format_last_message_from(_), do: [] 502 503 defp format_client_info({from, :dead}), 504 do: ["\nClient ", inspect(from), " is dead"] 505 506 defp format_client_info({from, :remote}), 507 do: ["\nClient ", inspect(from), " is remote on node ", inspect(node(from))] 508 509 defp format_client_info({_, {name, stacktrace}}), 510 do: ["\nClient ", inspect(name), " is alive\n" | format_stacktrace(stacktrace)] 511 512 defp format_client_info(_), 513 do: [] 514 515 defp format_reason({maybe_exception, [_ | _] = maybe_stacktrace} = reason) do 516 try do 517 format_stacktrace(maybe_stacktrace) 518 catch 519 :error, _ -> 520 {format_stop(reason), {reason, []}} 521 else 522 formatted_stacktrace -> 523 {formatted, reason} = maybe_normalize(maybe_exception, maybe_stacktrace) 524 {[formatted | formatted_stacktrace], {reason, maybe_stacktrace}} 525 end 526 end 527 528 defp format_reason(reason) do 529 {format_stop(reason), {reason, []}} 530 end 531 532 defp format_stop(reason) do 533 ["\n** (stop) " | Exception.format_exit(reason)] 534 end 535 536 # Erlang processes rewrite the :undef error to these reasons when logging 537 @gen_undef [:"module could not be loaded", :"function not exported"] 538 539 defp maybe_normalize(undef, [{mod, fun, args, _info} | _] = stacktrace) 540 when undef in @gen_undef and is_atom(mod) and is_atom(fun) do 541 cond do 542 is_list(args) -> 543 format_undef(mod, fun, length(args), undef, stacktrace) 544 545 is_integer(args) -> 546 format_undef(mod, fun, args, undef, stacktrace) 547 548 true -> 549 {format_stop(undef), undef} 550 end 551 end 552 553 defp maybe_normalize(reason, stacktrace) do 554 # If this is already an exception (even an ErlangError), we format it as an 555 # exception. Otherwise, we try to normalize it, and if it's normalized as an 556 # ErlangError we instead format it as an exit. 557 if is_exception(reason) do 558 {[?\n | Exception.format_banner(:error, reason, stacktrace)], reason} 559 else 560 case Exception.normalize(:error, reason, stacktrace) do 561 %ErlangError{} -> 562 {format_stop(reason), reason} 563 564 exception -> 565 {[?\n | Exception.format_banner(:error, exception, stacktrace)], exception} 566 end 567 end 568 end 569 570 defp format(kind, payload, stacktrace) do 571 [?\n, Exception.format_banner(kind, payload, stacktrace) | format_stacktrace(stacktrace)] 572 end 573 574 defp format_stacktrace(stacktrace) do 575 for entry <- stacktrace do 576 ["\n " | Exception.format_stacktrace_entry(entry)] 577 end 578 end 579 580 defp registered_name(name) when is_atom(name), do: [registered_name: name] 581 defp registered_name(_name), do: [] 582 583 defp format_mfa(mod, fun, :undefined), 584 do: [inspect(mod), ?., Code.Identifier.inspect_as_function(fun) | "/?"] 585 586 defp format_mfa(mod, fun, args), 587 do: Exception.format_mfa(mod, fun, args) 588 589 defp exit_reason(:exit, reason, stack), do: {reason, stack} 590 defp exit_reason(:error, reason, stack), do: {reason, stack} 591 defp exit_reason(:throw, value, stack), do: {{:nocatch, value}, stack} 592 593 ## Deprecated helpers 594 595 defp format_from([]), do: "" 596 defp format_from([from]), do: " (from #{inspect(from)})" 597 defp format_from([from, stacktrace]) when is_list(stacktrace), do: " (from #{inspect(from)})" 598 599 defp format_from([from, node_name]) when is_atom(node_name), 600 do: " (from #{inspect(from)} on #{inspect(node_name)})" 601 602 defp format_client([from]) do 603 "\nClient #{inspect(from)} is dead" 604 end 605 606 defp format_client([from, stacktrace]) when is_list(stacktrace) do 607 ["\nClient #{inspect(from)} is alive\n" | format_stacktrace(stacktrace)] 608 end 609 610 defp format_client(_) do 611 [] 612 end 613 614 defp format_undef(mod, fun, arity, undef, stacktrace) do 615 opts = [module: mod, function: fun, arity: arity, reason: undef] 616 exception = UndefinedFunctionError.exception(opts) 617 {[?\n | Exception.format_banner(:error, exception, stacktrace)], exception} 618 end 619end 620