logback.xml appender not used with cats IOApp - scala

I have a scala app which uses the AWS Kinesis Client Library.
I am using logback with the logstash encoder to format the logs from my app and the KCL as JSON.
My App is also written using cats.effects.IO.
import cats.effects._
object Main extends App {
run(args.toList).unsafeRunSync
def run(args: List[String]): IO[ExitCode] = { .. }
}
When the above code runs, logs from my app and from the KCL are correctly formatted through my JSON appender.
The problem arises when I try to use cats.effects.IOApp:
import cats.effects._
object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] = { .. }
}
When this version runs, the logs from my app are still correctly formatted through my JSON appender, but the logs from the KCL revert back to the default basic logger.
I have narrowed this down to the use of Fiber under the hood, and can reproduce the problem if I use run(args.toList).start.flatMap(_.join).unsafeRunSync which is essentially what IOApp is doing under the hood. I am running on the JVM, so this is the code that is running under the hood.
My logback.xml:
<appender name="json" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"level": "%level",
"message": "%message"
}
</pattern>
</pattern>
<nestedField>
<fieldName>properties</fieldName>
<providers>
<timestamp>
<fieldName>utcTimestamp</fieldName>
<pattern>yyyy-MM-dd'T'HH:mm:ss'Z'</pattern>
<timeZone>UTC</timeZone>
</timestamp>
<arguments/>
</providers>
</nestedField>
<stackTrace/>
</providers>
</encoder>
</appender>
<root level="info">
<appender-ref ref="json" />
</root>
</configuration>

Relying on the auto discovery of the logback.xml when there are multiple conflicting dependencies on the class path means there is no guarantee as to which will load first.
Since there is already a dependency for:
libraryDependencies += "org.slf4j" % "jcl-over-slf4j" % "1.7.21"
You just need to exclude the commons logger:
excludeDependencies += "commons-logging" % "commons-logging"
This way the KCL is forced to use the correct logger instead of having to choose.

Related

Add a hook to Sentry Logback to scrub data

I'm using the Logback SDK for Java to send events to Sentry as described in the documentation.
Snippet:
<conversionRule conversionWord="CUSTOM_CONVERSION_RULE"
converterClass="clazz..." />
...
<property scope="context" name="myEnc" value="%d{ISO8601,UTC} | %-5level | %-50thread | %-55logger{55} | %CUSTOM_CONVERSION_RULE" />
...
<appender name="SENTRY" class="io.sentry.logback.SentryAppender">
<dsn>...</dsn>
<encoder>${myEnc}</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
....
The initial problem was that the events sent to Sentry were not converted by my custom conversion rule. All the other appenders such as Console that use the property myEnc containing the conversion rule parse the data as expected. But it seems that the io.sentry.logback.SentryAppender in combination with the encoder somehow doesn't do that. The filter property is working as well as the dsn one, so I get the errors in Sentry but not with my custom parsing.
The version that I use for io.sentry.sentry-logback (and transitively sentry) is 1.7.24.
I then read about before-send hook from Sentry docs which is what I want to control what data is sent to Sentry and I had to upgrade to latest for that which is 3.1.3 at the time of writing this.
The Logback XML config changed a bit:
<appender name="SENTRY" class="io.sentry.logback.SentryAppender">
<options>
<dsn>...</dsn>
<beforeSend>????</beforeSend>
</options>
...
</appender>
From what I can see the before-send hook is exactly what I need to scrub the data when required because I don't want some info to be sent to Sentry. docs
Now, the second issue is that I don't know how to ref here a method. In the Java config there is a BiFunction that takes the event and can alter it. But I want to apply this hook to all my log events, that's why the only place it is configured is in the Logback SDK.
In Spring Boot for example there is a starter for Sentry and, off course, a bean that you can inject in the auto-configuration.
But, I'm using Scala with no Spring Boot.
Also, the project is already in prod so I cannot change lots of things and I'm looking for the smallest one that will allow me to add a hook to Logback's SDK for Sentry.
Here is the appender and it looks like (I'm not sure how it works) the options can be populated from XML and than pass to init that will take all them into account, including my before send hook.
I don't know if it's accepted to have two questions and only one referenced in the title but I didn't find a nicer way to ask/explain the problem, because one thing lead to another.
To summarize the questions:
Why that custom rule is not working with Logback Sentry's appender.
How can I let the appender know about my hook and use it.
Thanks in advance!
You can configure Sentry independently from appender configuration in logback.xml. For example:
public class Main {
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
static {
Sentry.init(options -> {
options.setDsn("PUT YOUR DSN HERE");
options.setBeforeSend((sentryEvent, o) -> {
sentryEvent.setTag("custom", "tag");
return sentryEvent;
});
});
}
public static void main(String[] args) {
LOGGER.error("oops");
}
}
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="sentry" class="io.sentry.logback.SentryAppender" />
<root level="debug">
<appender-ref ref="console"/>
<appender-ref ref="sentry"/>
</root>
</configuration>
Check complete code sample in github repo: https://github.com/maciej-scratches/sentry-logback-custom-config

Play for Scala: Logger prints "?" instead of class name

I have the following configuration in logback.xml:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %class{36} - %message%n%xException{10}</pattern>
</encoder>
</appender>
And log with the following statement
Logger.info("This is a message")
However the log prints ? instead of the class name:
[info] application - ? - This is a message
What is wrong?
Because when using the Logger api like this, you get a default logger, which does not know the class.
You need to use Logger like this:
val log = Logger(this.getClass)
log.info("This is a message")
See for more information the Play documentation on logging.

How do i use play ws library in normal sbt project instead of play?

When i tried using Play WS library in a normal sbt project instead of play project I was bound to use play.api.Play.current and got java.lang.RuntimeException: "There is no started application" when tried to run the application.
Usage in 2.4.x
import play.api.libs.ws.ning.NingWSClient
val wsClient = NingWSClient()
wsClient.url("http://wwww.something.com").get()
build.sbt :
libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.4.3"
Usage in 2.5.x
import play.api.libs.ws.ahc.AhcWSClient
implicit val actorSystem = ActorSystem()
implicit val materializer = ActorMaterializer()
wsClient.url("http://wwww.something.com").get()
//at the very end, to shutdown stuff cleanly :
wsClient.close()
actorSystem.terminate()
build.sbt :
libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.5.4"
Logs
As someone noted in the comment, by default you might get a bunch of verbose logs coming from the underlying async-http-client. One way to fix it is to start configuring a logback.xml, and placing it in src/main/resources
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- The logging pattern, you might want to adapt it -->
<pattern>%d %coloredLevel %t - %logger - %message%n%xException</pattern>
</encoder>
</appender>
<!-- Here you can change the levels of specific loggers -->
<logger name="somelogger" level="INFO" />
<!-- Default logging level for every logger -->
<root level="ERROR">
<appender-ref ref="STDOUT" />
</root>
</configuration>
To use play-ws outside of play see "Using WSClient" section of documentation: http://www.playframework.com/documentation/2.3.x/ScalaWS
val builder = new com.ning.http.client.AsyncHttpClientConfig.Builder()
val client = new play.api.libs.ws.ning.NingWSClient(builder.build())
val response = client.url(url).get()

log4j is not working with Jetty and liftweb app

I'm using a liftweb service over Jetty with log4j for logging.
here is how I setup the logger in Boot class:
val config = Properties.envOrElse("log4j.configuration","/props/syslog.log4j.xml")
val configFile = getClass.getResource(config)
Logger.setup = Full(Log4j.withFile(configFile))
When running the app as service (service jetty start), I can't see any log files written.
What am I doing wrong here?
This is my syslog.log4j.xml configuration file:
<root>
<level value="trace"/>
<appender-ref ref="CA"/>
<appender-ref ref="CA2"/>
</root>
Please read this wiki page to set up log4j:
https://www.assembla.com/spaces/liftweb/wiki/Logging

Configure logging with Lift

The Lift wiki page on logging states that a lot of setup is done automatically. Right now my problem is that I already have a running backend with its own logging configuration and a log4j.properties file in my classpath which should be used. There are also dependencies to log4j and SLF4j already in the classpath.
The main problem is that I get complete debug output for everything. Hibernate in particular -- which is very annoying.
I am using Lift 2.3-M1 and tried doing the following in the beginning of boot():
Logger.setup = Full(Log4j.withFile(getClass().getResource("/props/log4j.xml")))
The log4j.xml I am currently using is quickly hacked together to simply suppress DEBUG output.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration threshold="info" xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="CA" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%p] %c{2} %m%n"/>
</layout>
</appender>
<root>
<level value="info"/>
<appender-ref ref="CA"/>
</root>
</log4j:configuration>
When I create an errornous log4j.xml I also get an error from the SAXParser so it must be parsed. However I am still getting all DEBUG output. A second try was doing the following:
LiftRules.configureLogging = () => ()
Logger.setup = Full(Logback.withFile(getClass().getResource("/props/log4j.xml")))
Since I do not want to have Lift configure logging for me since the backend is already configured I would like to turn it completely off now. Oh and I also tried LogBoot.logSetup = () => false with no luck.
I would greatly appreciate any help on this issue.
The question got answered on the Lift mailing list.
The fix is to remove the logback dependency and to include both log4j and slf4j-log4j. No other configuration in boot() is required besides a valid default.log4j.xml.
I had a similar problem and came upon the following solution:
import net.liftweb.common.{ Empty, Logger }
import net.liftweb.http.Bootable
class BootLoader extends Bootable {
def boot = {
// other boot configuration ...
// prevent Lift from messing up my log4j config
Logger.setup = Empty
}
}