Crystal: Can't use Log.for in an instance variable.

Created on 12 Apr 2020  路  8Comments  路  Source: crystal-lang/crystal

Code:

class C
        getter log : Log

        def initialize(log = nil)
                # @log = log || Log.for(self)
                @log = Log.for(self)
        end
end

C.new

Output:

 > 4 |             @log : Log
                   ^---
Error: instance variable '@log' of C was not initialized directly in all of the 'initialize' methods, rendering it nilable. Indirect initialization is not supported.

Crystal 0.35.0-dev [74b7aa188] (2020-04-12)

question

Most helpful comment

That's not how Log works. You would route the log source to the memory backend using the top-level Log as shown in the docs:

https://crystal-lang.org/api/0.34.0/Log.html#configure-logging-explicitly-in-the-code

All 8 comments

Because self is passed to a method, there's a chance @log is read before it's used. The compiler prevents this.

One way to solve it is to make @log nilable. You can use getter! log : Log for this.

Using a nilable ivar won't help in this case. Log.for(self) only works in class scope, not inside a method. Log.for receives either a string or a class (which is then transformed into a string).

I'm not sure if you actually need the log as an instance variable. Usually you would just do Log = ::Log.for(self) at the class scope.

Either way, the equivalent invocation would be @log = Log.for(C), though @straight-shoota is correct that that's rather unneccesary vs a constant.

Main benefit would be able to inject a test logger. Otherwise currently there isn't a way, that I can think of ATM, that would allow you to mock it out for unit tests.

I suppose you would have to reopen the type in the spec and change the Log = xx const. Although wouldn't it complain that that constant is already defined?

MemoryBackend is provided by std, imagined for testing purposes.

Right yea, but how would you change the logger instance to use that backend if it is a constant? As you would run into Error: already initialized constant Foo::Log.

That's not how Log works. You would route the log source to the memory backend using the top-level Log as shown in the docs:

https://crystal-lang.org/api/0.34.0/Log.html#configure-logging-explicitly-in-the-code

Thank you all. The solution i went with:

class C
  Log = ::Log.for(self)

  def initialize(@log = Log)
  end
end

Why? I have a class with 2 distinct uses where I need debugging on one or the other but rarely both (Mostly just one). Turning them both on adds too much noise.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

asterite picture asterite  路  3Comments

oprypin picture oprypin  路  3Comments

asterite picture asterite  路  3Comments

lgphp picture lgphp  路  3Comments

jhass picture jhass  路  3Comments