• Stars
    star
    83
  • Rank 392,854 (Top 8 %)
  • Language
    Scala
  • License
    Other
  • Created over 4 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Blindsight is a Scala logging API with DSL based structured logging, fluent logging, semantic logging, flow logging, and context aware logging.

Blindsight

Maven central License Apache-2.0

Build Scala Steward badge

Blindsight is a logging library written in Scala that wraps SLF4J. The name is taken from Peter Watts' excellent first contact novel, Blindsight.

The core feature of Blindsight is that it is "type safe" -- rather than passing in arguments of type java.lang.Object, the API accepts only objects that can be converted into an Argument through the ToArgument type class.

val str: String = "string arg"
val number: Int = 1
val arg: Person = Person(name, age) // has a ToArgument[Person] type class instance
logger.info("{} {} {}", bool, number, person) // compiles fine

logger.info("{}", new Object()) // WILL NOT COMPILE

By adding type safety, Blindsight gives the application more control over how data is logged, rather than implicitly relying on the toString method to render data for logging purposes.

Blindsight adds useful features that solve several outstanding problems with logging:

  • Rendering structured logs in multiple formats through an AST, along with an optional format-independent DSL.
  • Providing thread-safe context to logs through context aware logging.
  • Time-based and targeted logging through conditional logging.
  • Dynamic targeted logging through scripting.
  • Easier "printf debugging" through macro based inspections.

Using Scala to break apart the SLF4J API also makes constructing new logging APIs much easier. You have the option of creating your own, depending on your use case:

  • Building up complex logging statements through fluent logging.
  • Enforcing user supplied type constraints through semantic logging.
  • Minimal-overhead tracing and causality tracking through flow logging.
  • Managing complex relationships and schema through JSON-LD.

Finally, there's also more advanced functionality to transform arguments and statements before entering SLF4J:

See the documentation for more details.

Blindsight and Echopraxia

If you are looking for a strict structured logging solution in Scala, please checkout echopraxia-plusscala.

Structured logging is optional in Blindsight, and it's possible to mix structured and "flat" arguments and markers into a logging statement. In contrast, echopraxia-plusscala requires structured logging and does not allow unstructured data as input.

Example

You can check out a "starter project" at https://github.com/tersesystems/blindsight-starter.

There's an example application at https://github.com/tersesystems/play-blindsight that integrates with Honeycomb Tracing using the flow logger:

trace.png

Dependencies

The only hard dependency is the SLF4J API. Structured logging is implemented for Logback with logstash-logback-encoder, but this is only a requirement if you are using structured logging.

Blindsight is a pure SLF4J wrapper: it delegates all logging through to the SLF4J API and does not configure or manage the SLF4J implementation at all.

Versions are published for Scala 2.11, 2.12, 2.13, and 3.0.0.

Install

See Setup for how to install Blindsight.

You can check out a "starter project" at https://github.com/tersesystems/blindsight-starter.

Because Blindsight uses a very recent version of Logstash that depends on Jackson 2.11.0, you may need to update your dependencies for the jackson-scala-module if you're using Play or Akka.

libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.11.0"

Usage

The easiest way to use Blindsight is to import the base package and the DSL:

import com.tersesystems.blindsight._
import com.tersesystems.blindsight.DSL._

To use a Blindsight Logger:

val logger = LoggerFactory.getLogger
logger.info("I am an SLF4J-like logger")

or in block form for diagnostic logging:

logger.debug { debug => debug("I am an SLF4J-like logger") }

Structured DSL:

import com.tersesystems.blindsight._
import com.tersesystems.blindsight.DSL._

logger.info("Logs with argument {}", bobj("array" -> Seq("one", "two", "three")))

Statement Interpolation:

val dayOfWeek = "Monday"
val temp = 72 

// macro expands this to:
// Statement("It is {} and the temperature is {} degrees.", Arguments(dayOfWeek, temp))
val statement: Statement = st"It is ${dayOfWeek} and the temperature is ${temp} degrees."

logger.info(statement)

Marker/Argument Type Classes:

case class Lotto(
  id: Long,
  winningNumbers: List[Int],
  winners: List[Winner],
  drawDate: Option[java.util.Date]
) {
  lazy val asBObject: BObject = "lotto" ->
      ("lotto-id"          -> id) ~
        ("winning-numbers" -> winningNumbers) ~
        ("draw-date"       -> drawDate.map(_.toString)) ~
        ("winners"         -> winners.map(w => w.asBObject))
}

object Lotto {
  implicit val toArgument: ToArgument[Lotto] = ToArgument { lotto => Argument(lotto.asBObject) }
}

val winners =
  List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22)))
val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, None)

logger.info("message {}", lotto) // auto-converted to structured output

JSON-LD:

implicit val nodeObjectToArgument: ToArgument[NodeObject] = ToArgument[NodeObject] { nodeObject =>
  Argument(BlindsightASTMapping.toBObject(nodeObject))
}

implicit val nodeObjectToMarkers: ToMarkers[NodeObject] = ToMarkers { nodeObject =>
  Markers(BlindsightASTMapping.toBObject(nodeObject))
}

implicit val nodeObjectToStatement: ToStatement[NodeObject] = ...

class Foo extends LDContext { // LDContext contains all the type safe bindings
  def sayHello(): Unit = {
    val willPerson = NodeObject(
      `@type`    -> "Person",
      `@id`      -> willId,
      givenName  -> "Will",
      familyName -> "Sargent",
      parent     -> parentId,
      occupation -> Occupation(
        estimatedSalary = MonetaryAmount(Currency.getInstance("USD"), 1),
        name = "Code Monkey"
      )
    )

    logger.info("as an argument {}", willPerson) // as an argument
    logger.info(Markers(willPerson), "as a marker") // as a marker
    
    logger.semantic[NodeObject].info(willPerson) // or as a statement
  }
}

Fluent logging:

logger.fluent.info
  .message("The Magic Words are")
  .argument(Arguments("Squeamish", "Ossifrage"))
  .logWithPlaceholders()

Semantic logging:

// log only user events
logger.semantic[UserEvent].info(userEvent)

// Works well with refinement types
import eu.timepit.refined.api.Refined
import eu.timepit.refined.string._
import eu.timepit.refined._
logger.semantic[String Refined Url].info(refineMV(Url)("https://tersesystems.com"))

Flow logging:

import com.tersesystems.blindsight.flow._

implicit def flowBehavior[B]: FlowBehavior[B] = new SimpleFlowBehavior

val arg1: Int = 1
val arg2: Int = 2
val result:Int = logger.flow.trace(arg1 + arg2)

Conditional logging:

logger.withCondition(booleanCondition).info("Only logs when condition is true")

logger.info.when(booleanCondition) { info => info("when true") }

Context aware logging:

import DSL._

// Add key/value pairs with DSL and return a logger
val markerLogger = logger.withMarker(bobj("userId" -> userId))

// log with generated logger
markerLogger.info("Logging with user id added as a context marker!")

// can retrieve state markers
val contextMarkers: Markers = markerLogger.markers

Entry Transformation:

val logger = LoggerFactory.getLogger
               .withEntryTransform(e => e.copy(message = e.message + " IN BED"))

logger.info("You will discover your hidden talents")

Event Buffer:

val queueBuffer = EventBuffer(1)
val logger      = LoggerFactory.getLogger.withEventBuffer(queueBuffer)

logger.info("Hello world")

val event = queueBuffer.head

Scripting:

val scriptHandle = new ScriptHandle {
  override def isInvalid: Boolean = false // on file modification, etc
  override val script: String =
    """import strings as s from 'std.tf';
      |alias s.ends_with? as ends_with?;
      |
      |library blindsight {
      |  function evaluate: (long level, string enc, long line, string file) ->
      |    if (ends_with?(enc, "specialMethodName")) then true
      |    else false;
      |}
      |""".stripMargin
  override def report(e: Throwable): Unit = e.printStackTrace()
}
val scriptManager = new ScriptManager(scriptHandle) 
val location = new ScriptAwareLocation(scriptManager)

def specialMethodName = {
  // inside the specialMethodName method here :-)
  logger.debug.when(location.here) { log => 
    log("script allows selective logging by method or by line")
  }
}

Inspections:

import com.tersesystems.blindsight.inspection.InspectionMacros._

decorateIfs(dif => logger.debug(s"${dif.code} = ${dif.result}")) {
  if (System.currentTimeMillis() % 17 == 0) {
    println("branch 1")
  } else if (System.getProperty("derp") == null) {
    println("branch 2")
  } else {
    println("else branch")
  }
}

Benchmarks

Benchmarks are available here.

License

Blindsight is released under the "Apache 2" license. See LICENSE for specifics and copyright declaration.

More Repositories

1

terse-logback

Structured Logging, Tracing, and Observability with Logback
Java
190
star
2

blacklite

"Fast as internal ring buffer" Logback/Log4J2 appender using SQLite with zstandard dictionary compression and rollover.
Java
60
star
3

echopraxia

Java Structured Logging API for Logback, Log4J2, and JUL
Java
52
star
4

securitybuilder

Fluent builders with typesafe API for the JCA
Java
43
star
5

jvmsounds

Play memory allocation rate and GC events as sine wave and percussion, respectively.
Java
32
star
6

ocaps

Object capability (ocap) tools and macros for Scala.
JavaScript
17
star
7

debugjsse

Debug JSSE Provider
Java
14
star
8

jmxbuilder

Fluent API for building JMX Beans
Java
9
star
9

terse-logback-showcase

An example Play project showing terse-logback
Java
9
star
10

blindsight-starter

Starter SBT project with blindsight logging configured
Scala
6
star
11

echopraxia-spring-boot-example

Example app showing Echopraxia in a Spring Boot App
Java
5
star
12

dynamic-debug-logging

Docker Compose PoC showing dynamic debug logging in the cloud using a structured logging framework.
Java
4
star
13

echopraxia-plusscala

Scala API for Echopraxia
Scala
3
star
14

jcaprovider

A serviceloader based SPI service and java agent for JCA providers.
Java
3
star
15

echopraxia-scalafix

Scalafix rules for echopraxia loggers
Scala
2
star
16

echopraxia-plusakka

Akka extensions for Echopraxia
Scala
2
star
17

play-blindsight

Example "observability" project using Play with Blindsight and Honeycomb
Scala
2
star
18

jmxmvc

Create and register mbeans and hierarchy through MVC model using MBeanInterceptor
Java
1
star
19

echopraxia-examples

Echopraxia Examples: structured logging and dynamic complex conditions demos.
Java
1
star