Hey, I tried to debug how I can catch some things.
I created a new local application by mix new my_logger command.
My config/config.exs:
use Mix.Config
config :logger,
backends: [MyLogger],
handle_otp_reports: true,
handle_sasl_reports: true
and here is my main module:
defmodule MyLogger do
use GenEvent
def init(_) do
{:ok, %{}}
end
def handle_call({:configure, options}, _state) do
{:ok, :ok, %{}}
end
def handle_event({_level, gl, {Logger, _, _, _}}, state) when node(gl) != node() do
{:ok, state}
end
def handle_event({level, group_leader, {Logger, message, {date, time}, details}}, state) do
IO.inspect "catched?"
# stacktrace here ...
{:ok, state}
end
end
at end I run iex -S mix command.
In Interactive Elixir I can see already catched 3 log messages.
It works also when I try to execute an: Logger macros such as info.
I checked what data I could collect and itâs really good.
From here I have some questions:
I know that &System.stacktrace/0 works only for exceptions. Is there any other way (logger configuration?) to get a stacktrace when message is logged?
Is there any way to catch also all raise (I mean allow crash, but send info about it to Logger). I already tried set some logger configuration variables like @OvermindDL1 already wrote, but I donât got a âcatched?â message.
Is it possible (by configuration change?) to got passed argument values in stacktrace (optionally only for Logger), so I could easily reproduce an logged message in user specific situations?
Unsure about that, worst case you could throw and catch an exception to get a stacktrace⊠sounds slow though
Any uncaught raiseâs or any uncaught exception in general should kill its process and log to :sasl, which you can catch through there. However âcaughtâ exceptions are âcaughtâ, thus handled, thus nothing reported, so you will never get a âcatchedâ message of any form, only uncaught exceptions. Youâd have to decorate raise with a replacement macro or something to do that Iâd think?
Stacktraces do not hold that much information, especially because that information might be gone by the time the exception is thrown if those names end up unused by the point the exception is thrown and, say, garbage collected, so Iâm unsure if that would even be possible in all cases. At the very least you should have the information for the current application, module, function name, file name, and line number of a log, beyond that you need to add the extra information yourself (which you could easily add the passed in arguments to by adding them to the process log of metadata by calling something like Logger.metadata([:func_args, [blah, bleep]]) but that will get overwritten of course if calling any other function before that point. If you made a custom function (replace def with your own) then you could decorate a log call as much as you want safely, but youâd have to change your function definitions everywhere that youâd want to log that information. Remember that Elixir is about explicitness, minimize magical stuff.
I could easily be wrong on much of this, I use Logger to log, Iâve not made my own back-end yet. ^.^
try do
raise "oops"
rescue
exception ->
IO.inspect System.stacktrace
end
This returns stacktrace, but only for: :proc_lib, GenEvent and my logger.
hmm, I would like to catch all raise without try do ⊠rescue ⊠end code.
I do not expect too many mistakes, but I want to catch them all like Pokemons trainer
Third question was optional. Itâs not big problem to detect what goes wrong without know a value of function arguments.
Youâd have to do it at the call site via a custom log function, it would not work within your logger. Sorry I forgot to say that. :-)[quote=âEiji, post:3, topic:2237â]
hmm, I would like to catch all raise without try do ⊠rescue ⊠end code.
I do not expect too many mistakes, but I want to catch them all like Pokemons trainer
[/quote]
Hehe, you should be able to by turning on :sasl and such. Raiseâs without tryâs will eventually kill the process that it is in, which will be reported by :sasl. If it does not kill the process then there was a try that caught it.
The pointer honestly probably still exists on the stack itself until the function returns (thus no GCâing it), but no clue how it would be accessible as for all intents and purposes it is dead at those times after its last use. A custom log function/macro could keep it alive though, but Iâm pretty sure that would require a custom def regardless unless you use one of the :after_compile hooks or soâŠ
Ahahah, I want to add one more question to my previous response:
One more question:
Is it good to catch raise (by try do ...) to get stacktrace and call Logger.error instead of just raise an exception in my libraries in methods with names ends with â!â character?
but you was faster - just love this fast forum
It looks interesting, because I will not have stacktrace in external libraries as âEctoâ, which is even recommended and simply beautiful!
Itâs probably not work for me, because I tried to raise in iex. It probably have some catches to let console live for >10 years
It looks like complicated. I want to write code that anyone can understand without âhacksâ. As I wrote it already. It was only optional question. Anyway thanks for your that answer.
The shell, sadly, does not live on an OTP tree (it lives âinâ it, but not managed by it, it is manually managed), so you will not get messages from it, but you will from anything attached to the OTP tree.
You could easily make a defLog or so macro that people can replaced def with that adds in all kinds of extra useful log metadata or so (and clears it when it exits).
I will add one more field to database table to save other_data map and after my initial release feel free to create PR for that. At now Iâm feel that my Elixir knowledge is not so good to write so advanced macro like that.
It could be useful when in job you work with beginners. You could just instruct them to use âspecialâ macro to let inspect some data and let them learn âspecialâ user cases.