8

UPDATE: SOLVED!!!
Please see the answer I added below


Does anyone know if there is a way to do logging that outputs real line numbers to the console? I'm trying to get into Scala here, but without being able to get basic stuff like this that I depend on, it's really hard to get much going.

I have set up slf4s to wrap with slf4j - log4j - jcl-over-slf4j. The problem is I get line numbers that do not match at all. The are much higher line numbers than the Scala class even contains. Is this because the line numbers are actually Java-intermediate line numbers?

Is there any EASY way to get set up logging that meets these requirements?:

  1. interoperable, working with both java & scala
  2. as easy to change the logging level of individual packages as easily as log4j
  3. provides ACCURATE line numbers.

Thanks!

Jamie

jpswain
  • 14,642
  • 8
  • 58
  • 63

4 Answers4

7

I have found that logback (by Ceki Gülcü) works great and preserves line numbers too!
(And it works as a replacement for log4j: Awesome!)

import ch.qos.logback._
import org.slf4j._

object Main {

    def logger = LoggerFactory.getLogger("Main")
    var thingy = {
        x:Int =>
        logger.info("x=" + x)
        x + 1
    }
    def main(args: Array[String]) {
        logger.info("Hello.")
        logger.info("Hello again!")

        val myInts : List[Int] = List(-25,1,5,20)

        val myInts2 : List[Int] = myInts.filter { x:Int => x > 0 }

        logger.info("my ints2:" + myInts2)

        val myInts3 = myInts2.map(p =>  p * 2 )
        logger.info("my ints3:" + myInts3)

        logger.info(thingy(1) + "")
    }
}

For anyone struggling to get started with Scala, this is what I did to get the basic skeleton up:

1) Download sbt-launcher.jar and put it someplace like /opt/
I used "sbt-launch-0.7.5.RC0.jar"

2) Create a bash script as a shortcut to the sbt launcher in nano /opt/bin/sbt:

#!/bin/bash
java -jar /opt/sbt-launch-0.7.5.RC0.jar "$@"

(make it executable)

$ sudo chmod ug+x ./sbt

Make sure it's in your path too.

3) Create and configure the sbt project:

$ mkdir ./sc01
$ cd ./sc01
$ sbt
$ mkdir ./project/build</pre>
$ nano ./project/build/Project.scala</pre>

put this in there:

import sbt._

class sc01(info: ProjectInfo) extends DefaultProject(info)
{
    // dependencies
    val logback_core = "ch.qos.logback" % "logback-core" % "0.9.24" % "compile" //LGPL 2.1
    val logback_classic = "ch.qos.logback" % "logback-classic" % "0.9.24" % "compile" //LGPL 2.1
    val log4j_over_slf4j = "org.slf4j" % "log4j-over-slf4j" % "1.6.1"


   // if you are going to have any unmanaged (manually-added) jars
   //    def baseDirectories = "lib"
   //    def extraJars = descendents(baseDirectories, "*.jar")
   //    override def unmanagedClasspath = super.unmanagedClasspath +++ extraJars

    // tasks - easy to define
    lazy val hi = task { println("Hello World"); None }

    // classpath
    //override def mainScalaSourcePath = "src"

}

4) Paste the stuff from above into Main:

$ nano ./src/main/scala/Main.scala

5) I almost forgot! put this in /src/main/resources/logback.xml
(it's required to get the line numbers)

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
     ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %line --- %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

6) $ sbt

Now you should be in the sbt console in your shell:

> update
> compile
> run

Hope this helps.

koppor
  • 19,079
  • 15
  • 119
  • 161
jpswain
  • 14,642
  • 8
  • 58
  • 63
  • Interesting feedback. I'll give it a try. +1 (don't forget that you can select your own answer as the official one) – VonC Mar 08 '11 at 05:03
  • 1
    You're explanation here is _so_ much better then the sbt documentation. Wish i had found this answer sooner. – srparish Jun 30 '11 at 03:16
  • @srparish I'm glad it helped you! I've gotten so much help in my relatively short programming career from blog posts/stack overflow/mailing lists that I am happy to be able to contribute back anything I can offer. – jpswain Jun 30 '11 at 15:51
  • line numbers are way off, must be the compiled class file the line number refers to – virtualeyes Apr 10 '12 at 18:33
  • @virtualeyes Hmm, would you mind taking a look at the example I posted here: https://github.com/jpswain/DummySbtScalaWicket.git Can you try that and check out the line numbers. If that works for you, then try and see how your project is different? I'd like to hear if there are any special cases to look out for. Thanks! – jpswain Apr 12 '12 at 00:03
2

Update 2016: a library like lihaoyi/sourcecode does include a logging use case with a new approach:

You can use sourcecode.File and sourcecode.Line to define log functions that automatically capture their line number and file-name

def log(foo: String)(implicit line: sourcecode.Line, file: sourcecode.File) = {
  println(s"${file.value}:${line.value} $foo")
}

log("Foooooo") // sourcecode/shared/src/test/scala/sourcecode/Tests.scala:86 Fooooo

This can be handy for letting you see where the log lines are coming from, without tediously tagging every log statement with a unique prefix.
Furthermore, this happens at compile time, and is thus orders of magnitude faster than getting this information by generating stack traces, and works on Scala.js where stack-inspection does not.
Lastly, if you want additional information such as method names, class names, or packages to be provided to your logging function, you can easily do so by asking for the sourcecode.Name or sourcecode.FullName or sourcecode.Pkg implicits.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

As you are commenting in Scala logging question, getting precise line number information in Scala is hard.

  • Why does Scala not just overload the existing infrastructure by saving absolute offsets instead of line numbers in the LineNumberTable?
    Another way of addressing could be by numbering the tokens instead of the actual offsets into source files.
  • While I love the idea of indexing by tokens, this means that any tool capable of using the debug information would need to have access to a full parser.
    Another possibility would be to reformat the file according to some strictly-defined set of rules, and then continue to use line numbering.
  • I started working on improving the debugging experience for Scala programs, and one of the sore-points is indeed line numbers. Ideally there would be support for more than just line numbers. I am looking at JSR 45 (Debugging support for other languages). I am not sure yet if that is enough, but maybe a Scala stratum could use your scheme.
    I think the better way is to provide additional, Scala specific, debugging information in classfile attributes or annotations. As far as I know, JDI does not give access to classfile attributes, nor to annotations, but there are some tricks we could use to get to them. This way we'd preserve existing functionality, and allow tools to do more when they know about Scala attributes.

(Note: Scalate reports having done a similar work in scalate-24 for a different kind of source files)

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

I would recommend taking a look at Scribe. It's a complete logging solution for Scala using Macros to generate line numbers and other information at compile-time so there is no reduction in speed, and it's built-in so you don't have to use something like sourcecode and integrate it manually:

https://github.com/outr/scribe

darkfrog
  • 1,053
  • 8
  • 29